chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:32:25 +08:00
commit e014feafe1
2285 changed files with 1131979 additions and 0 deletions
+313
View File
@@ -0,0 +1,313 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"testing"
"github.com/dolthub/go-mysql-server/enginetest/scriptgen/setup"
"github.com/jackc/pgx/v5/pgtype"
"github.com/dolthub/go-mysql-server/sql"
)
// This is a correctness test based on the AdaptiveEncodingTest in Dolt.
// Because Doltgres serializes its results over a Postgres connection, we can't inspect the encoding
// of the result row. But we can still confirm that using adaptable encoding doesn't change any expected behavior.
func makeTestBytes(size int, firstbyte byte) []byte {
bytes := make([]byte, size)
bytes[0] = firstbyte
return bytes
}
func makeTestVarbit(sizeInBits int) pgtype.Bits {
numBytes := sizeInBits / 8
leftoverBits := sizeInBits % 8
bytes := make([]byte, numBytes)
for i := 0; i < numBytes; i++ {
bytes[i] = 0xaa
}
// this is a little annoying, but if we have leftover bits, we need to pad out the remaining bits in the last byte with 0s
endingByte := byte(0)
for i := 0; i < leftoverBits/2; i++ {
endingByte |= 0b10 << (6 - i*2)
}
if leftoverBits > 0 {
bytes = append(bytes, endingByte)
}
return pgtype.Bits{
Bytes: bytes,
Len: int32(sizeInBits),
Valid: true,
}
}
// A 4000 byte file starting with 0x01 and then consisting of all zeros.
// This is larger than default target tuple size for outlining adaptive types.
// We expect a tuple to always store this value out-of-band
var fullSizeString = string(makeTestBytes(4000, 1))
// A 2000 byte file starting with 0x02 and then consisting of all zeros.
// This is over half of the default target tuple size for outlining adaptive types.
// We expect a tuple to be able to store this value inline once, but not twice.
var halfSizeString = string(makeTestBytes(2000, 2))
// A 10 byte file starting with 0x03 and then consisting of 10 zero bytes.
// This is file is smaller than an address hash.
// We expect a tuple to never store this value out-of-band.
var tinyString = string(makeTestBytes(10, 3))
// A 4000 byte file starting with ascii byte 1 and then consisting of all zeros.
// This is larger than default target tuple size for outlining adaptive types.
// We expect a tuple to always store this value out-of-band
var fullSizeVarbit = makeTestVarbit(4000)
// A 2000 byte file starting with ascii byte 1 and then consisting of all zeros.
// This is over half of the default target tuple size for outlining adaptive types.
// We expect a tuple to be able to store this value inline once, but not twice.
var halfSizeVarbit = makeTestVarbit(2000)
// A 10 byte file starting with ascii byte 1 and then consisting of 10 zero bytes.
// This is file is smaller than an address hash.
// We expect a tuple to never store this value out-of-band.
var tinyVarbit = makeTestVarbit(10)
func TestAdaptiveEncodingText(t *testing.T) {
fullSizeOutOfLineRepr := fullSizeString
columnTypes := []string{"varchar", "text"}
for _, columnType := range columnTypes {
t.Run(columnType, func(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Adaptive Encoding With One Column",
SetUpScript: setup.SetupScript{
fmt.Sprintf(`create table blobt (i char(1) primary key, b %s);`, columnType),
fmt.Sprintf(`create table blobt2 (i char(2) primary key, b1 %s, b2 %s);`, columnType, columnType),
`insert into blobt values
('F', LOAD_FILE('testdata/fullSize')),
('H', LOAD_FILE('testdata/halfSize')),
('T', LOAD_FILE('testdata/tinyFile'))`,
},
Assertions: []ScriptTestAssertion{
{
Query: "select b from blobt where i = 'F'",
Expected: []sql.Row{{fullSizeString}},
},
{
// Files that can fit within a tuple are stored inline.
Query: "select b from blobt where i = 'H'",
Expected: []sql.Row{{halfSizeString}},
},
{
// An inlined adaptive column can be used in a filter.
Query: "select i from blobt where b = LOAD_FILE('testdata/fullSize')",
Expected: []sql.Row{{"F"}},
},
{
// An out-of-line adaptive column can be used in a filter.
Query: "select i from blobt where b = LOAD_FILE('testdata/halfSize')",
Expected: []sql.Row{{"H"}},
},
},
},
{
Name: "Adaptive Encoding With Two Columns",
SetUpScript: setup.SetupScript{
fmt.Sprintf(`create table blobt2 (i char(2) primary key, b1 %s, b2 %s);`, columnType, columnType),
`insert into blobt2 values
('FF', LOAD_FILE('testdata/fullSize'), LOAD_FILE('testdata/fullSize')),
('HF', LOAD_FILE('testdata/halfSize'), LOAD_FILE('testdata/fullSize')),
('TF', LOAD_FILE('testdata/tinyFile'), LOAD_FILE('testdata/fullSize')),
('FH', LOAD_FILE('testdata/fullSize'), LOAD_FILE('testdata/halfSize')),
('HH', LOAD_FILE('testdata/halfSize'), LOAD_FILE('testdata/halfSize')),
('TH', LOAD_FILE('testdata/tinyFile'), LOAD_FILE('testdata/halfSize')),
('FT', LOAD_FILE('testdata/fullSize'), LOAD_FILE('testdata/tinyFile')),
('HT', LOAD_FILE('testdata/halfSize'), LOAD_FILE('testdata/tinyFile')),
('TT', LOAD_FILE('testdata/tinyFile'), LOAD_FILE('testdata/tinyFile'))`,
},
Assertions: []ScriptTestAssertion{
{
// When a tuple with multiple adaptive columns is too large, columns are moved out-of-band from left to right.
// However, strings smaller than the address size (20 bytes) are never stored out-of-band.
Query: "select i, b1, b2 from blobt2",
Expected: []sql.Row{
{"FF", fullSizeString, fullSizeString},
{"HF", halfSizeString, fullSizeString},
{"TF", tinyString, fullSizeString},
{"FH", fullSizeString, halfSizeString},
{"HH", halfSizeString, halfSizeString},
{"TH", tinyString, halfSizeString},
{"FT", fullSizeString, tinyString},
{"HT", halfSizeString, tinyString},
{"TT", tinyString, tinyString},
},
},
{
// An adaptive column can be used in a filter when it doesn't have the same encoding in all rows.
Query: "select i from blobt2 where b1 = LOAD_FILE('testdata/halfSize')",
Expected: []sql.Row{{"HF"}, {"HH"}, {"HT"}},
},
{
// An adaptive column can be used in a filter when it doesn't have the same encoding in all rows.
Query: "select i from blobt2 where b2 = LOAD_FILE('testdata/halfSize')",
Expected: []sql.Row{{"FH"}, {"HH"}, {"TH"}},
},
{
// Test creating an index on an adaptive encoding column, matching against out-of-band values
Query: "CREATE INDEX bidx ON blobt2 (b1)",
},
{
Query: "select i, b1 FROM blobt2 WHERE b1 LIKE '\x01%'",
Expected: []sql.Row{
{"FF", fullSizeOutOfLineRepr},
{"FH", fullSizeOutOfLineRepr},
{"FT", fullSizeOutOfLineRepr},
},
},
{
// Test creating an index on an adaptive encoding column, matching against inline values
Query: "CREATE INDEX bidx2 ON blobt2 (b2)",
},
{
Query: "select i, b2 FROM blobt2 WHERE b2 LIKE '\x02%'",
Expected: []sql.Row{
{"FH", halfSizeString},
{"HH", halfSizeString},
{"TH", halfSizeString},
},
},
{
// Tuples containing adaptive columns should be independent of how the tuple was created.
// And adaptive values are always outlined starting from the left.
// This means that in a table with two adaptive columns where both columns were previously stored out-of line,
// Decreasing the size of the second column may allow both columns to be stored inline.
Query: "UPDATE blobt2 SET b2 = LOAD_FILE('testdata/tinyFile') WHERE i = 'HH'",
},
{
Query: "select i, b1, b2 from blobt2 where i = 'HH'",
Expected: []sql.Row{{"HH", halfSizeString, tinyString}},
},
{
// Similar to the above, dropping a column can change whether the other column is inlined.
Query: "ALTER TABLE blobt2 DROP COLUMN b2",
},
{
Query: "select i, b1 from blobt2",
Expected: []sql.Row{
{"FF", fullSizeString},
{"HF", halfSizeString},
{"TF", tinyString},
{"FH", fullSizeString},
{"HH", halfSizeString},
{"TH", tinyString},
{"FT", fullSizeString},
{"HT", halfSizeString},
{"TT", tinyString},
},
},
},
},
})
})
}
}
func TestAdaptiveEncodingVarbit(t *testing.T) {
columnType := "varbit"
RunScripts(t, []ScriptTest{
{
Name: "Adaptive Encoding With One Column",
SetUpScript: setup.SetupScript{
fmt.Sprintf(`create table blobt (i char(1) primary key, b %s);`, columnType),
fmt.Sprintf(`create table blobt2 (i char(2) primary key, b1 %s, b2 %s);`, columnType, columnType),
fmt.Sprintf(`insert into blobt values
('F', LOAD_FILE('testdata/fullSizeVarbit')::%s),
('H', LOAD_FILE('testdata/halfSizeVarbit')::%s),
('T', LOAD_FILE('testdata/tinyFileVarbit')::%s)`, columnType, columnType, columnType),
},
Assertions: []ScriptTestAssertion{
{
Query: "select b from blobt where i = 'F'",
Expected: []sql.Row{{fullSizeVarbit}},
},
{
// Files that can fit within a tuple are stored inline.
Query: "select b from blobt where i = 'H'",
Expected: []sql.Row{{halfSizeVarbit}},
},
{
// An inlined adaptive column can be used in a filter.
Query: "select i from blobt where b = LOAD_FILE('testdata/fullSizeVarbit')::varbit",
Expected: []sql.Row{{"F"}},
},
{
// An out-of-line adaptive column can be used in a filter.
Query: "select i from blobt where b = LOAD_FILE('testdata/halfSizeVarbit')::varbit",
Expected: []sql.Row{{"H"}},
},
},
},
{
Name: "Adaptive Encoding With Two Columns",
SetUpScript: setup.SetupScript{
fmt.Sprintf(`create table blobt2 (i char(2) primary key, b1 %s, b2 %s);`, columnType, columnType),
fmt.Sprintf(`insert into blobt2 values
('FF', LOAD_FILE('testdata/fullSizeVarbit')::%s, LOAD_FILE('testdata/fullSizeVarbit')::%s),
('HF', LOAD_FILE('testdata/halfSizeVarbit')::%s, LOAD_FILE('testdata/fullSizeVarbit')::%s),
('TF', LOAD_FILE('testdata/tinyFileVarbit')::%s, LOAD_FILE('testdata/fullSizeVarbit')::%s),
('FH', LOAD_FILE('testdata/fullSizeVarbit')::%s, LOAD_FILE('testdata/halfSizeVarbit')::%s),
('HH', LOAD_FILE('testdata/halfSizeVarbit')::%s, LOAD_FILE('testdata/halfSizeVarbit')::%s),
('TH', LOAD_FILE('testdata/tinyFileVarbit')::%s, LOAD_FILE('testdata/halfSizeVarbit')::%s),
('FT', LOAD_FILE('testdata/fullSizeVarbit')::%s, LOAD_FILE('testdata/tinyFileVarbit')::%s),
('HT', LOAD_FILE('testdata/halfSizeVarbit')::%s, LOAD_FILE('testdata/tinyFileVarbit')::%s),
('TT', LOAD_FILE('testdata/tinyFileVarbit')::%s, LOAD_FILE('testdata/tinyFileVarbit')::%s)`, columnType, columnType,
columnType, columnType, columnType, columnType, columnType, columnType, columnType, columnType,
columnType, columnType, columnType, columnType, columnType, columnType, columnType, columnType),
},
Assertions: []ScriptTestAssertion{
{
// When a tuple with multiple adaptive columns is too large, columns are moved out-of-band from left to right.
// However, strings smaller than the address size (20 bytes) are never stored out-of-band.
Query: "select i, b1, b2 from blobt2 order by 1",
Expected: []sql.Row{
{"FF", fullSizeVarbit, fullSizeVarbit},
{"FH", fullSizeVarbit, halfSizeVarbit},
{"FT", fullSizeVarbit, tinyVarbit},
{"HF", halfSizeVarbit, fullSizeVarbit},
{"HH", halfSizeVarbit, halfSizeVarbit},
{"HT", halfSizeVarbit, tinyVarbit},
{"TF", tinyVarbit, fullSizeVarbit},
{"TH", tinyVarbit, halfSizeVarbit},
{"TT", tinyVarbit, tinyVarbit},
},
},
{
// An adaptive column can be used in a filter when it doesn't have the same encoding in all rows.
Query: "select i from blobt2 where b1 = LOAD_FILE('testdata/halfSizeVarbit')::varbit",
Expected: []sql.Row{{"HF"}, {"HH"}, {"HT"}},
},
{
// An adaptive column can be used in a filter when it doesn't have the same encoding in all rows.
Query: "select i from blobt2 where b2 = LOAD_FILE('testdata/halfSizeVarbit')::varbit",
Expected: []sql.Row{{"FH"}, {"HH"}, {"TH"}},
},
},
},
})
}
File diff suppressed because it is too large Load Diff
+141
View File
@@ -0,0 +1,141 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import "testing"
// TestAlterStatements tests ALTER statements other than ALTER TABLE, mostly for stub functionality.
// These tests should move into their respective test files as real functionality for these ALTER statements is added.
func TestAlterStatements(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "alter database",
Assertions: []ScriptTestAssertion{
{
Query: "ALTER DATABASE postgres OWNER TO foo",
ExpectedNotices: []ExpectedNotice{
{
Severity: "WARNING",
Message: "OWNER TO is unsupported and ignored",
},
},
},
},
},
{
Name: "alter sequence",
SetUpScript: []string{
"CREATE SEQUENCE testseq",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER SEQUENCE testseq OWNER TO foo",
ExpectedNotices: []ExpectedNotice{
{
Severity: "WARNING",
Message: "OWNER TO is unsupported and ignored",
},
},
},
},
},
{
Name: "alter type",
SetUpScript: []string{
"CREATE TYPE testtype AS ENUM ('a', 'b', 'c')",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER TYPE testtype OWNER TO foo",
ExpectedNotices: []ExpectedNotice{
{
Severity: "WARNING",
Message: "OWNER TO is unsupported and ignored",
},
},
},
},
},
{
Name: "alter function",
SetUpScript: []string{
"CREATE FUNCTION testfunc() RETURNS int AS $$ BEGIN RETURN 1; END; $$ LANGUAGE plpgsql",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER FUNCTION testfunc() OWNER TO foo",
ExpectedNotices: []ExpectedNotice{
{
Severity: "WARNING",
Message: "OWNER TO is unsupported and ignored",
},
},
},
},
},
{
Name: "alter procedure",
SetUpScript: []string{
"CREATE TABLE test (v1 INT8);",
"CREATE PROCEDURE testproc() AS $$ BEGIN INSERT INTO test VALUES (1); END; $$ LANGUAGE plpgsql;",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER PROCEDURE testproc() OWNER TO foo;",
ExpectedNotices: []ExpectedNotice{
{
Severity: "WARNING",
Message: "OWNER TO is unsupported and ignored",
},
},
},
},
},
{
Name: "alter schema",
SetUpScript: []string{
"CREATE SCHEMA testschema",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER schema testschema OWNER TO foo",
ExpectedNotices: []ExpectedNotice{
{
Severity: "WARNING",
Message: "OWNER TO is unsupported and ignored",
},
},
},
},
},
{
Name: "alter view",
SetUpScript: []string{
"CREATE VIEW testview AS SELECT 1",
},
Assertions: []ScriptTestAssertion{
{
Query: "ALTER VIEW testview OWNER TO foo",
ExpectedNotices: []ExpectedNotice{
{
Severity: "WARNING",
Message: "OWNER TO is unsupported and ignored",
},
},
},
},
},
})
}
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestAsOf(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Single table",
SetUpScript: []string{
`CREATE TABLE test (a INT)`,
`INSERT INTO test VALUES (1)`,
`SELECT DOLT_COMMIT('-Am', 'new table')`,
`INSERT INTO test VALUES (2)`,
`SELECT DOLT_COMMIT('-am', 'new row')`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM test AS OF 'HEAD^' as t1`,
Expected: []sql.Row{
{1},
},
},
{
Query: `SELECT * FROM test AS OF 'HEAD'`,
Expected: []sql.Row{
{1},
{2},
},
},
},
},
{
Name: "Join",
SetUpScript: []string{
`CREATE TABLE test (a INT)`,
`INSERT INTO test VALUES (1)`,
`SELECT DOLT_COMMIT('-Am', 'new table')`,
`INSERT INTO test VALUES (2)`,
`SELECT DOLT_COMMIT('-am', 'new row')`,
`CREATE TABLE test2 (b INT)`,
`INSERT INTO test2 VALUES (1)`,
`SELECT DOLT_COMMIT('-Am', 'new table')`,
`INSERT INTO test2 VALUES (2)`,
`SELECT DOLT_COMMIT('-am', 'new row')`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM test AS OF 'HEAD~3' t1 join test2 AS OF 'HEAD~' t2 on t1.a = t2.b`,
Expected: []sql.Row{
{1, 1},
},
},
{
Query: `SELECT * FROM test AS OF 'HEAD~3' t1 join test2 AS t2 on t1.a = t2.b`,
Expected: []sql.Row{
{1, 1},
},
},
{
Query: `SELECT * FROM test t1 join test2 AS OF 'HEAD~' AS t2 on t1.a = t2.b`,
Expected: []sql.Row{
{1, 1},
},
},
{
Query: `SELECT * FROM test AS OF 'HEAD~3' t1 cross join test2 AS OF 'HEAD~' t2`,
Expected: []sql.Row{
{1, 1},
},
},
{
Query: `SELECT * FROM test AS OF 'HEAD' t1 cross join test2 AS OF 'HEAD~' t2`,
Expected: []sql.Row{
{1, 1},
{2, 1},
},
},
},
},
{
Name: "Syntax variations", // There are no unit tests for the parser, so test all variations of the AS OF syntax
SetUpScript: []string{
`CREATE TABLE test (a INT)`,
`INSERT INTO test VALUES (1)`,
`SELECT DOLT_COMMIT('-Am', 'new table')`,
`INSERT INTO test VALUES (2)`,
`SELECT DOLT_COMMIT('-am', 'new row')`,
`CREATE TABLE test2 (b INT)`,
`INSERT INTO test2 VALUES (1)`,
`SELECT DOLT_COMMIT('-Am', 'new table')`,
`INSERT INTO test2 VALUES (2)`,
`SELECT DOLT_COMMIT('-am', 'new row')`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM test AS OF 'HEAD~3' AS t1 join test2 AS OF 'HEAD' AS t2 on t1.a = t2.b`,
Expected: []sql.Row{
{1, 1},
},
},
{
Query: `SELECT * FROM test AS OF 'HEAD~3' t1 join test2 AS OF 'HEAD' t2 on t1.a = t2.b`,
Expected: []sql.Row{
{1, 1},
},
},
{
Query: `SELECT * FROM test AS t1 join test2 AS OF 'HEAD~' t2 on t1.a = t2.b`,
Expected: []sql.Row{
{1, 1},
},
},
{
Query: `SELECT * FROM test AS OF SYSTEM TIME 'HEAD~3' join test2 AS t2 on test.a = t2.b`,
Expected: []sql.Row{
{1, 1},
},
},
},
},
})
}
+373
View File
@@ -0,0 +1,373 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"strings"
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
// TestAuthQuick is modeled after the QuickPrivilegeTest in GMS, so please refer to the documentation there:
// https://github.com/dolthub/go-mysql-server/blob/main/enginetest/queries/priv_auth_queries.go
func TestAuthQuick(t *testing.T) {
// Statements that are run before every test (the state that all tests start with):
// CREATE USER tester PASSWORD 'password';
// CREATE SCHEMA mysch;
// CREATE SCHEMA othersch;
// CREATE TABLE mysch.test (pk BIGINT PRIMARY KEY, v1 BIGINT);
// CREATE TABLE mysch.test2 (pk BIGINT PRIMARY KEY, v1 BIGINT);
// CREATE TABLE othersch.test (pk BIGINT PRIMARY KEY, v1 BIGINT);
// CREATE TABLE othersch.test2 (pk BIGINT PRIMARY KEY, v1 BIGINT);
// INSERT INTO mysch.test VALUES (0, 0), (1, 1);
// INSERT INTO mysch.test2 VALUES (0, 1), (1, 2);
// INSERT INTO othersch.test VALUES (1, 1), (2, 2);
// INSERT INTO othersch.test2 VALUES (1, 1), (2, 2);
type QuickPrivilegeTest struct {
Focus bool
Queries []string
Expected []sql.Row
ExpectedErr string
}
tests := []QuickPrivilegeTest{
{
Queries: []string{
"GRANT SELECT ON ALL TABLES IN SCHEMA mysch TO tester;",
"SELECT * FROM mysch.test;",
},
Expected: []sql.Row{{0, 0}, {1, 1}},
},
{
Queries: []string{
"GRANT SELECT ON ALL TABLES IN SCHEMA mysch TO tester;",
"SELECT * FROM mysch.test2;",
},
Expected: []sql.Row{{0, 1}, {1, 2}},
},
{
Queries: []string{
"GRANT SELECT ON mysch.test TO tester;",
"SELECT * FROM mysch.test;",
},
Expected: []sql.Row{{0, 0}, {1, 1}},
},
{
Queries: []string{
"GRANT SELECT ON mysch.test TO tester;",
"SELECT * FROM mysch.test2;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT SELECT ON ALL TABLES IN SCHEMA othersch TO tester;",
"SELECT * FROM mysch.test;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT SELECT ON othersch.test TO tester;",
"SELECT * FROM mysch.test;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT SELECT ON othersch.test TO tester;",
"SELECT * FROM mysch.test;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"CREATE SCHEMA newsch;",
},
ExpectedErr: "permission denied for database",
},
{
Queries: []string{
"GRANT CREATE ON DATABASE postgres TO tester;",
"CREATE SCHEMA newsch;",
},
},
{
Queries: []string{
"GRANT CREATE ON DATABASE postgres TO tester;",
"CREATE SCHEMA newsch;",
"DROP SCHEMA newsch;",
},
ExpectedErr: "permission denied for schema",
},
{
Queries: []string{
"CREATE TABLE mysch.new_table (pk BIGINT PRIMARY KEY);",
},
ExpectedErr: "permission denied for schema",
},
{
Queries: []string{
"GRANT CREATE ON SCHEMA mysch TO tester;",
"CREATE TABLE mysch.new_table (pk BIGINT PRIMARY KEY);",
},
},
{
Queries: []string{
"CREATE ROLE new_role;",
},
ExpectedErr: "does not have permission",
},
{
Queries: []string{
"ALTER ROLE tester CREATEROLE;",
"CREATE ROLE new_role;",
},
},
{
Queries: []string{
"CREATE USER new_user;",
},
ExpectedErr: "does not have permission",
},
{
Queries: []string{
"ALTER ROLE tester SUPERUSER;",
"CREATE USER new_user;",
},
},
{
Queries: []string{
"CREATE USER new_user;",
"DROP USER new_user;",
},
ExpectedErr: "does not have permission",
},
{
Queries: []string{
"CREATE USER new_user;",
"ALTER ROLE tester CREATEROLE;",
"DROP USER new_user;",
},
},
{
Queries: []string{
"CREATE USER new_user SUPERUSER;",
"ALTER ROLE tester CREATEROLE;",
"DROP USER new_user;",
},
ExpectedErr: "does not have permission",
},
{
Queries: []string{
"CREATE USER new_user SUPERUSER;",
"ALTER ROLE tester SUPERUSER;",
"DROP USER new_user;",
},
},
{
Queries: []string{
"DELETE FROM mysch.test WHERE pk >= 0;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT DELETE ON ALL TABLES IN SCHEMA mysch TO tester;",
"DELETE FROM mysch.test WHERE pk >= 0;",
},
},
{
Queries: []string{
"GRANT DELETE ON mysch.test TO tester;",
"DELETE FROM mysch.test WHERE pk >= 0;",
},
},
{
Queries: []string{
"CREATE USER tester2;",
"GRANT DELETE ON ALL TABLES IN SCHEMA mysch TO tester2;",
"GRANT tester2 TO tester;",
"DELETE FROM mysch.test WHERE pk >= 0;",
},
},
{
Queries: []string{
"SELECT * FROM mysch.test JOIN mysch.test2 ON test.pk = test2.pk;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT SELECT ON mysch.test TO tester;",
"SELECT * FROM mysch.test JOIN mysch.test2 ON test.pk = test2.pk;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT SELECT ON mysch.test2 TO tester;",
"SELECT * FROM mysch.test JOIN mysch.test2 ON test.pk = test2.pk;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT SELECT ON mysch.test TO tester;",
"GRANT SELECT ON mysch.test2 TO tester;",
"SELECT * FROM mysch.test JOIN mysch.test2 ON test.pk = test2.pk;",
},
Expected: []sql.Row{{0, 0, 0, 1}, {1, 1, 1, 2}},
},
{
Queries: []string{
"CREATE USER tester2;",
"GRANT SELECT ON mysch.test2 TO tester2;",
"GRANT tester2 TO tester;",
"SELECT * FROM mysch.test JOIN mysch.test2 ON test.pk = test2.pk;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"CREATE USER tester2;",
"GRANT SELECT ON mysch.test TO tester2;",
"GRANT SELECT ON mysch.test2 TO tester2;",
"GRANT tester2 TO tester;",
"SELECT * FROM mysch.test JOIN mysch.test2 ON test.pk = test2.pk;",
},
Expected: []sql.Row{{0, 0, 0, 1}, {1, 1, 1, 2}},
},
{
Queries: []string{
"CREATE TABLE mysch.new_table (pk BIGINT PRIMARY KEY);",
"DROP TABLE mysch.new_table;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA mysch TO tester;",
"REVOKE DROP ON ALL TABLES IN SCHEMA mysch FROM tester;",
"CREATE TABLE mysch.new_table (pk BIGINT PRIMARY KEY);",
"DROP TABLE mysch.new_table;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"CREATE TABLE mysch.new_table (pk BIGINT PRIMARY KEY);",
"GRANT DROP ON mysch.new_table TO tester;",
"DROP TABLE mysch.new_table;",
},
},
{
Queries: []string{
"CREATE TABLE mysch.new_table (pk BIGINT PRIMARY KEY);",
"GRANT postgres TO tester;",
"DROP TABLE mysch.new_table;",
},
},
{
Queries: []string{
"CREATE ROLE new_role;",
"DROP ROLE new_role;",
},
ExpectedErr: "does not have permission",
},
{
Queries: []string{
"ALTER ROLE tester CREATEROLE;",
"CREATE ROLE new_role;",
"DROP ROLE new_role;",
},
},
{
Queries: []string{
"INSERT INTO mysch.test VALUES (9, 9);",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT INSERT ON ALL TABLES IN SCHEMA mysch TO tester;",
"INSERT INTO mysch.test VALUES (9, 9);",
},
},
{
Queries: []string{
"GRANT INSERT ON mysch.test TO tester;",
"INSERT INTO mysch.test VALUES (9, 9);",
},
},
{
Queries: []string{
"UPDATE mysch.test SET v1 = 0;",
},
ExpectedErr: "permission denied for table",
},
{
Queries: []string{
"GRANT UPDATE ON ALL TABLES IN SCHEMA mysch TO tester;",
"UPDATE mysch.test SET v1 = 0;",
},
},
{
Queries: []string{
"GRANT UPDATE ON mysch.test TO tester;",
"UPDATE mysch.test SET v1 = 0;",
},
},
}
// Here we'll convert each quick test into a standard test
scriptTests := make([]ScriptTest, len(tests))
for testIdx, test := range tests {
scriptTests[testIdx] = ScriptTest{
Name: strings.Join(test.Queries, "\n > "),
Database: "",
SetUpScript: []string{
"CREATE USER tester PASSWORD 'password';",
"CREATE SCHEMA mysch;",
"CREATE SCHEMA othersch;",
"CREATE TABLE mysch.test (pk BIGINT PRIMARY KEY, v1 BIGINT);",
"CREATE TABLE mysch.test2 (pk BIGINT PRIMARY KEY, v1 BIGINT);",
"CREATE TABLE othersch.test (pk BIGINT PRIMARY KEY, v1 BIGINT);",
"CREATE TABLE othersch.test2 (pk BIGINT PRIMARY KEY, v1 BIGINT);",
"INSERT INTO mysch.test VALUES (0, 0), (1, 1);",
"INSERT INTO mysch.test2 VALUES (0, 1), (1, 2);",
"INSERT INTO othersch.test VALUES (1, 1), (2, 2);",
"INSERT INTO othersch.test2 VALUES (1, 1), (2, 2);",
},
Assertions: make([]ScriptTestAssertion, len(test.Queries)),
Focus: test.Focus,
}
for queryIdx := 0; queryIdx < len(test.Queries)-1; queryIdx++ {
scriptTests[testIdx].Assertions[queryIdx] = ScriptTestAssertion{
Query: test.Queries[queryIdx],
SkipResultsCheck: true,
Username: "postgres",
Password: "password",
}
}
scriptTests[testIdx].Assertions[len(test.Queries)-1] = ScriptTestAssertion{
Query: test.Queries[len(test.Queries)-1],
Expected: test.Expected,
ExpectedErr: test.ExpectedErr,
Username: "tester",
Password: "password",
}
}
RunScripts(t, scriptTests)
}
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"os"
"path/filepath"
"runtime"
"github.com/cockroachdb/errors"
)
var benchmarkFolder BenchmarkFolderLocation // benchmarkFolder is the disk location of the benchmark folder
// BenchmarkFolderLocation is the location of this project's root folder.
type BenchmarkFolderLocation struct {
path string
}
// GetBenchmarkFolder returns the location of the benchmark folder (scripts/mini_benchmark). This is used to find the
// absolute position of our output files.
func GetBenchmarkFolder() (BenchmarkFolderLocation, error) {
_, currentFileLocation, _, ok := runtime.Caller(0)
if !ok {
return BenchmarkFolderLocation{}, errors.Errorf("failed to fetch the location of the current file")
}
benchmarkFolder = BenchmarkFolderLocation{filepath.Clean(filepath.Join(filepath.Dir(currentFileLocation),
"../../../scripts/mini_sysbench"))}
return benchmarkFolder, nil
}
// MoveRoot returns a new BenchmarkFolderLocation that defines the root at the new path. The parameter should be
// relative to the current root.
func (root BenchmarkFolderLocation) MoveRoot(relativePath string) BenchmarkFolderLocation {
return BenchmarkFolderLocation{filepath.Clean(filepath.Join(root.path, relativePath))}
}
// GetAbsolutePath returns the absolute path of the given path, which should be relative to the project's root
// folder.
func (root BenchmarkFolderLocation) GetAbsolutePath(relativePath string) string {
return filepath.ToSlash(filepath.Join(root.path, relativePath))
}
// Exists returns whether the file or directory at the given path (relative to the root path) exists. Returns an error
// if the check was unable to be completed.
func (root BenchmarkFolderLocation) Exists(relativePath string) (bool, error) {
_, err := os.Stat(root.GetAbsolutePath(relativePath))
if os.IsNotExist(err) {
return false, nil
} else if err != nil {
return false, err
}
return true, nil
}
// ReadDir is equivalent to os.ReadDir, except that it uses the root path and the given relative path.
func (root BenchmarkFolderLocation) ReadDir(relativePath string) ([]os.DirEntry, error) {
return os.ReadDir(root.GetAbsolutePath(relativePath))
}
// ReadFile is equivalent to os.ReadFile, except that it uses the root path and the given relative path.
func (root BenchmarkFolderLocation) ReadFile(relativePath string) ([]byte, error) {
return os.ReadFile(root.GetAbsolutePath(relativePath))
}
// WriteFile is equivalent to os.WriteFile, except that it uses the root path and the given relative path.
func (root BenchmarkFolderLocation) WriteFile(relativePath string, data []byte, perm os.FileMode) error {
directory := filepath.ToSlash(filepath.Dir(relativePath))
exists, err := root.Exists(directory)
if err != nil {
return err
}
if !exists {
if err = os.MkdirAll(root.GetAbsolutePath(directory), 0644); err != nil {
return err
}
}
return os.WriteFile(root.GetAbsolutePath(relativePath), data, perm)
}
// init is used to load the location of the benchmark folder.
func init() {
var err error
benchmarkFolder, err = GetBenchmarkFolder()
if err != nil {
panic(err)
}
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"math"
"os"
"strings"
)
// AllowedVariance represents the amount that must change before a result is noteworthy. The number represents a whole
// percentage, so "10" equals "10%".
const AllowedVariance = 10
// main analyzes two separate runs of scripts/quick_sysbench.sh, and creates a table comparing the differences. This
// table is intended for display in a GitHub PR.
func main() {
prBenchmark, err := benchmarkFolder.ReadFile("results1.log")
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
mainBenchmark, err := benchmarkFolder.ReadFile("results2.log")
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
}
prSections := SectionResults(string(prBenchmark))
mainSections := SectionResults(string(mainBenchmark))
sb := strings.Builder{}
sb.WriteString("| | Main | PR | |\n")
sb.WriteString("| --- | --- | --- | --- |\n")
for _, kv := range GetMapKVsSorted(mainSections) {
mainSection := kv.Value
prSection, ok := prSections[mainSection.Test]
if !ok {
sb.WriteString(fmt.Sprintf("| %s | %.2f/s | ${\\color{red}DNF}$ | |\n",
mainSection.Test, mainSection.Time))
continue
}
percentChange := math.Floor(((prSection.Time/mainSection.Time)-1.0)*1000.0) / 10.0
if percentChange > AllowedVariance { // Greatly positive
sb.WriteString(fmt.Sprintf("| %s | %.2f/s | ${\\color{lightgreen}%.2f/s}$ | ${\\color{lightgreen}+%.1f\\\\%%}$ |\n",
mainSection.Test, mainSection.Time, prSection.Time, percentChange))
} else if percentChange < -AllowedVariance { // Greatly negative
sb.WriteString(fmt.Sprintf("| %s | %.2f/s | ${\\color{red}%.2f/s}$ | ${\\color{red}%.1f\\\\%%}$ |\n",
mainSection.Test, mainSection.Time, prSection.Time, percentChange))
} else if percentChange > 0 { // Positive
sb.WriteString(fmt.Sprintf("| %s | %.2f/s | %.2f/s | +%.1f%% |\n",
mainSection.Test, mainSection.Time, prSection.Time, percentChange))
} else if percentChange < 0 { // Negative
sb.WriteString(fmt.Sprintf("| %s | %.2f/s | %.2f/s | %.1f%% |\n",
mainSection.Test, mainSection.Time, prSection.Time, percentChange))
} else { // No Change
sb.WriteString(fmt.Sprintf("| %s | %.2f/s | %.2f/s | 0.0%% |\n",
mainSection.Test, mainSection.Time, prSection.Time))
}
}
fmt.Println(sb.String())
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"cmp"
"sort"
)
// KeyValue represents an entry in a map.
type KeyValue[K comparable, V any] struct {
Key K
Value V
}
// GetMapKVs returns the map's KeyValue entries as an unsorted slice.
func GetMapKVs[K comparable, V any](m map[K]V) []KeyValue[K, V] {
allEntries := make([]KeyValue[K, V], len(m))
i := 0
for k, v := range m {
allEntries[i] = KeyValue[K, V]{Key: k, Value: v}
i++
}
return allEntries
}
// GetMapKVsSorted returns the map's KeyValue entries as a sorted slice. The keys are sorted in ascending order.
func GetMapKVsSorted[K cmp.Ordered, V any](m map[K]V) []KeyValue[K, V] {
allEntries := GetMapKVs(m)
sort.Slice(allEntries, func(i, j int) bool {
return allEntries[i].Key < allEntries[j].Key
})
return allEntries
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"strconv"
"strings"
)
// Section represents a benchmark section.
type Section struct {
Test string
Time float64 // This is in number of iterations per second
}
// SectionResults creates a section for each test.
func SectionResults(fileData string) map[string]Section {
sections := make(map[string]Section)
for {
headerStartIdx := strings.Index(fileData, `----`)
if headerStartIdx == -1 {
break
}
headerEndIdx := strings.Index(fileData[headerStartIdx+4:], `----`) + headerStartIdx + 4
if headerEndIdx == headerStartIdx+4 {
break
}
headerFull := fileData[headerStartIdx : headerEndIdx+4]
endingHeaderIdx := strings.LastIndex(fileData, headerFull)
if endingHeaderIdx == -1 {
break
}
section := Section{
Test: headerFull[4 : len(headerFull)-4],
Time: -1,
}
sectionText := strings.TrimSpace(fileData[len(headerFull):endingHeaderIdx])
fileData = fileData[endingHeaderIdx+len(headerFull):]
for _, line := range strings.Split(sectionText, "\n") {
if strings.Contains(line, `queries:`) {
parenIdx := strings.Index(line, `(`)
perSecIdx := strings.Index(line, ` per sec.)`)
if parenIdx != -1 && perSecIdx != -1 {
timeString := line[parenIdx+1 : perSecIdx]
parsedTime, err := strconv.ParseFloat(timeString, 64)
if err == nil {
section.Time = parsedTime
}
}
break
}
}
if section.Time == -1 {
continue
}
sections[section.Test] = section
}
return sections
}
+120
View File
@@ -0,0 +1,120 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"strconv"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/stretchr/testify/require"
)
// TestBindingWithOidZero tests the behavior of binding parameters when the client specifies a zero OID for any of
// the parameters.
func TestBindingWithOidZero(t *testing.T) {
// Start up a test server
ctx, connection, controller := CreateServer(t, "postgres")
defer func() {
connection.Close(ctx)
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()
conn := connection.Default
// Create a table to insert into
_, err := connection.Exec(ctx, "CREATE TABLE my_table (id INT, name varchar(100));")
require.NoError(t, err)
args := [][]byte{
[]byte(strconv.Itoa(42)),
[]byte("Alice"),
}
paramOIDs := []uint32{0, 123}
paramFormats := []int16{0, 0}
sql := "INSERT INTO my_table (id, name) VALUES ($1, $2);"
// Execute a query with the zero OID and assert that we don't get an error
resultReader := conn.PgConn().ExecParams(ctx, sql, args, paramOIDs, paramFormats, nil)
result := resultReader.Read()
require.NoError(t, result.Err)
}
func TestIssue2386(t *testing.T) {
// https://github.com/dolthub/doltgresql/issues/2386
ctx, connection, controller := CreateServer(t, "postgres")
defer func() {
connection.Close(ctx)
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()
conn := connection.Default
_, err := connection.Exec(ctx, "CREATE TABLE users (id INT PRIMARY KEY, name TEXT NOT NULL);")
require.NoError(t, err)
_, err = connection.Exec(ctx, "INSERT INTO users VALUES (1, 'alice'), (2, 'bob'), (3, 'carol'), (4, 'dave');")
require.NoError(t, err)
targetIDs := []int32{1, 3}
rows, err := conn.Query(ctx,
`SELECT id, name FROM users WHERE id = ANY($1)`,
targetIDs,
)
require.NoError(t, err)
defer rows.Close()
i := 0
for rows.Next() {
var id int32
var name string
err = rows.Scan(&id, &name)
require.NoError(t, err)
switch i {
case 0:
require.Equal(t, int32(1), id)
require.Equal(t, "alice", name)
case 1:
require.Equal(t, int32(3), id)
require.Equal(t, "carol", name)
default:
t.FailNow()
}
i++
}
}
func TestBindingWithTextArray(t *testing.T) {
ctx, connection, controller := CreateServer(t, "postgres")
defer func() {
connection.Close(ctx)
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()
conn := connection.Default
m := pgtype.NewMap()
textArray := []string{"foo", "bar"}
plan := m.PlanEncode(pgtype.TextArrayOID, pgtype.BinaryFormatCode, textArray)
encodedArr, err := plan.Encode(textArray, nil)
require.NoError(t, err)
args := [][]byte{encodedArr}
paramOIDs := []uint32{1009}
paramFormats := []int16{1}
sql := "SELECT $1::text[]"
resultReader := conn.PgConn().ExecParams(ctx, sql, args, paramOIDs, paramFormats, nil)
result := resultReader.Read()
require.NoError(t, result.Err)
}
+204
View File
@@ -0,0 +1,204 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCoercion(t *testing.T) {
RunScriptsWithoutNormalization(t, []ScriptTest{
{
Name: "Raw Literals",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 0`,
Expected: []sql.Row{{int32(0)}},
},
{
Query: `SELECT 0.5`,
Expected: []sql.Row{{Numeric("0.5")}},
},
{
Query: `SELECT 0.50`,
Expected: []sql.Row{{Numeric("0.50")}},
},
{
Query: `SELECT -0.5`,
Expected: []sql.Row{{Numeric("-0.5")}},
},
{
Query: `SELECT 12345671297673227365.5123624235623456`,
Expected: []sql.Row{{Numeric("12345671297673227365.5123624235623456")}},
},
{
Query: `SELECT 1`,
Expected: []sql.Row{{int32(1)}},
},
{
Query: `SELECT -1`,
Expected: []sql.Row{{int32(-1)}},
},
{
Query: `SELECT 70000`,
Expected: []sql.Row{{int32(70000)}},
},
{
Query: `SELECT 5000000000`,
Expected: []sql.Row{{int64(5000000000)}},
},
{
Query: `SELECT 9223372036854775808`,
Expected: []sql.Row{{Numeric("9223372036854775808")}},
},
{
Query: `SELECT ''`,
Expected: []sql.Row{{""}},
},
{
Query: `SELECT 'test'`,
Expected: []sql.Row{{"test"}},
},
{
Query: `SELECT '0'`,
Expected: []sql.Row{{"0"}},
},
},
},
{
Name: "Math Functions",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT abs(1)`,
Expected: []sql.Row{{int32(1)}},
},
{
Query: `SELECT abs(1.5)`,
Expected: []sql.Row{{Numeric("1.5")}},
},
{
Query: `SELECT abs(5000000000)`,
Expected: []sql.Row{{int64(5000000000)}},
},
{
Query: `SELECT abs(9223372036854775808)`,
Expected: []sql.Row{{Numeric("9223372036854775808")}},
},
{
Query: `SELECT abs('1')`,
Expected: []sql.Row{{float64(1)}},
},
{
Query: `SELECT abs('1.5')`,
Expected: []sql.Row{{float64(1.5)}},
},
{
Query: `SELECT abs('12345671297673227365.5123624235623456')`,
Expected: []sql.Row{{float64(1.2345671297673228e+19)}},
},
{
Query: `SELECT abs('NaN'::numeric)`,
Expected: []sql.Row{{Numeric("NaN")}},
},
{
Query: `SELECT abs('Inf'::numeric)`,
Expected: []sql.Row{{Numeric("Infinity")}},
},
{
Query: `SELECT abs('-infinity'::numeric)`,
Expected: []sql.Row{{Numeric("Infinity")}},
},
{
Query: `SELECT abs('0'::numeric)`,
Expected: []sql.Row{{Numeric("0")}},
},
{
Query: `SELECT abs('-0.50'::numeric)`,
Expected: []sql.Row{{Numeric("0.50")}},
},
{
Query: `SELECT factorial('1')`,
Expected: []sql.Row{{Numeric("1")}},
},
{
Query: `SELECT factorial('1.5')`,
ExpectedErr: "invalid input",
},
{
Query: `SELECT ceil('NaN'::numeric)`,
Expected: []sql.Row{{Numeric("NaN")}},
},
{
Query: `SELECT ceil('Inf'::numeric)`,
Expected: []sql.Row{{Numeric("Infinity")}},
},
{
Query: `SELECT ceil('-infinity'::numeric)`,
Expected: []sql.Row{{Numeric("-Infinity")}},
},
{
Query: `SELECT floor('NaN'::numeric)`,
Expected: []sql.Row{{Numeric("NaN")}},
},
{
Query: `SELECT floor('Inf'::numeric)`,
Expected: []sql.Row{{Numeric("Infinity")}},
},
{
Query: `SELECT floor('-infinity'::numeric)`,
Expected: []sql.Row{{Numeric("-Infinity")}},
},
{
Query: `SELECT ln('NaN'::numeric)`,
Expected: []sql.Row{{Numeric("NaN")}},
},
{
Query: `SELECT ln('Inf'::numeric)`,
Expected: []sql.Row{{Numeric("Infinity")}},
},
{
Query: `SELECT ln('-infinity'::numeric)`,
ExpectedErr: `cannot take logarithm of a negative number`,
},
{
Query: `SELECT log('NaN'::numeric)`,
Expected: []sql.Row{{Numeric("NaN")}},
},
{
Query: `SELECT log('Inf'::numeric)`,
Expected: []sql.Row{{Numeric("Infinity")}},
},
{
Query: `SELECT log('-infinity'::numeric)`,
ExpectedErr: `cannot take logarithm of a negative number`,
},
{
Query: `SELECT min_scale('NaN'::numeric)`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT min_scale('Inf'::numeric)`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT min_scale('-infinity'::numeric)`,
Expected: []sql.Row{{nil}},
},
},
},
})
}
+118
View File
@@ -0,0 +1,118 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
var CommandTagTests = []ScriptTest{
{
Name: "set",
Assertions: []ScriptTestAssertion{
{
Query: "SET extra_float_digits = 3",
ExpectedTag: "SET",
},
},
},
{
Name: "show",
Assertions: []ScriptTestAssertion{
{
Query: "SHOW extra_float_digits",
ExpectedTag: "SHOW",
},
},
},
{
Name: "create database",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE mydb",
ExpectedTag: "CREATE DATABASE",
},
},
},
{
Name: "insert",
SetUpScript: []string{
"CREATE TABLE table0 (id int, name text)",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO table0 VALUES (1,'Dolt'), (2,'Doltgres'), (3,'DoltHub')",
ExpectedTag: "INSERT 0 3",
},
{
Query: "SELECT * FROM table0 order by id",
Expected: []sql.Row{{1, "Dolt"}, {2, "Doltgres"}, {3, "DoltHub"}},
},
{
Query: "SELECT * FROM table0",
ExpectedTag: "SELECT 3",
},
},
},
{
Name: "update",
SetUpScript: []string{
"CREATE TABLE table0 (id int, name text)",
"INSERT INTO table0 VALUES (1,'Dolt'), (2,'Doltgres'), (3,'DoltHub')",
},
Assertions: []ScriptTestAssertion{
{
Query: "UPDATE table0 SET id = 4 WHERE name = 'Doltgres'",
ExpectedTag: "UPDATE 1",
},
{
Query: "SELECT * FROM table0 order by id",
Expected: []sql.Row{{1, "Dolt"}, {3, "DoltHub"}, {4, "Doltgres"}},
},
{
Query: "SELECT * FROM table0 WHERE name <> 'Dolt'",
ExpectedTag: "SELECT 2",
},
},
},
{
Name: "delete",
SetUpScript: []string{
"CREATE TABLE table0 (id int, name text)",
"INSERT INTO table0 VALUES (1,'Dolt'), (2,'Doltgres'), (3,'DoltHub')",
},
Assertions: []ScriptTestAssertion{
{
Query: "DELETE FROM table0",
ExpectedTag: "DELETE 3",
},
{
Query: "SELECT * FROM table0 order by id",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM table0",
ExpectedTag: "SELECT 0",
},
},
},
}
func TestCommandTag(t *testing.T) {
RunScripts(t, CommandTagTests)
}
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
// TestCompositeUniqueIndexExtendedAndAdaptive tests composite UNIQUE indexes that combine an
// extended-encoded type (uuid) with an adaptive-encoded type (text, unbounded varchar).
// Regression test for https://github.com/dolthub/doltgresql/issues/2886
func TestCompositeUniqueIndexExtendedAndAdaptive(t *testing.T) {
RunScripts(t, []ScriptTest{
// Keyless tables
{
Name: "keyless: UNIQUE(uuid, text) — insert into empty table",
SetUpScript: []string{
"CREATE TABLE t (iid uuid, slug text, UNIQUE(iid, slug));",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t VALUES ('11111111-1111-1111-1111-111111111111', 'hello');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES ('22222222-2222-2222-2222-222222222222', 'hello');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES ('11111111-1111-1111-1111-111111111111', 'world');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES ('11111111-1111-1111-1111-111111111111', 'hello');",
ExpectedErr: "unique",
},
},
},
{
Name: "keyless: UNIQUE(text, uuid) — column order reversed",
SetUpScript: []string{
"CREATE TABLE t (slug text, iid uuid, UNIQUE(slug, iid));",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t VALUES ('hello', '11111111-1111-1111-1111-111111111111');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES ('hello', '11111111-1111-1111-1111-111111111111');",
ExpectedErr: "unique",
},
{
Query: "INSERT INTO t VALUES ('world', '11111111-1111-1111-1111-111111111111');",
Expected: []sql.Row{},
},
},
},
{
Name: "keyless: UNIQUE(uuid, varchar) — unbounded varchar",
SetUpScript: []string{
"CREATE TABLE t (iid uuid, slug varchar, UNIQUE(iid, slug));",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t VALUES ('11111111-1111-1111-1111-111111111111', 'hello');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES ('11111111-1111-1111-1111-111111111111', 'hello');",
ExpectedErr: "unique",
},
},
},
{
Name: "keyless: UNIQUE(uuid, text) — NULL uuid skips unique check",
SetUpScript: []string{
"CREATE TABLE t (iid uuid, slug text, UNIQUE(iid, slug));",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t VALUES (NULL, 'hello');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES (NULL, 'hello');",
Expected: []sql.Row{},
},
},
},
// Keyed tables
{
Name: "keyed: UNIQUE(uuid, text) on table with primary key",
SetUpScript: []string{
"CREATE TABLE t (pk int primary key, iid uuid, slug text, UNIQUE(iid, slug));",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t VALUES (1, '11111111-1111-1111-1111-111111111111', 'hello');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES (2, '11111111-1111-1111-1111-111111111111', 'hello');",
ExpectedErr: "unique",
},
{
Query: "INSERT INTO t VALUES (3, '11111111-1111-1111-1111-111111111111', 'world');",
Expected: []sql.Row{},
},
},
},
{
Name: "keyed: UNIQUE(uuid, varchar) — unbounded varchar on table with primary key",
SetUpScript: []string{
"CREATE TABLE t (pk int primary key, iid uuid, slug varchar, UNIQUE(iid, slug));",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t VALUES (1, '11111111-1111-1111-1111-111111111111', 'hello');",
Expected: []sql.Row{},
},
{
Query: "INSERT INTO t VALUES (2, '11111111-1111-1111-1111-111111111111', 'hello');",
ExpectedErr: "unique",
},
},
},
})
}
File diff suppressed because it is too large Load Diff
+403
View File
@@ -0,0 +1,403 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"path/filepath"
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/stretchr/testify/require"
)
func TestCopy(t *testing.T) {
absTestDataDir, err := filepath.Abs("testdata")
require.NoError(t, err)
RunScripts(t, []ScriptTest{
{
Name: "tab delimited with header",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key);",
"INSERT INTO test VALUES (0), (1);",
"CREATE TABLE test_info (id int, info varchar(255), test_pk int, primary key(id), foreign key (test_pk) references test(pk));",
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY test_info FROM STDIN WITH (HEADER);",
CopyFromStdInFile: "tab-load-with-header.sql",
},
{
Query: "SELECT * FROM test_info order by 1;",
Expected: []sql.Row{
{4, "string for 4", 1},
{5, "string for 5", 0},
{6, "string for 6", 0},
},
},
},
},
{
Name: "tab delimited with header and column names",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key);",
"INSERT INTO test VALUES (0), (1);",
"CREATE TABLE test_info (id int, info varchar(255), test_pk int, primary key(id), foreign key (test_pk) references test(pk));",
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY test_info (id, info, test_pk) FROM STDIN WITH (HEADER);",
CopyFromStdInFile: "tab-load-with-header.sql",
},
{
Query: "SELECT * FROM test_info order by 1;",
Expected: []sql.Row{
{4, "string for 4", 1},
{5, "string for 5", 0},
{6, "string for 6", 0},
},
},
},
},
{
Name: "tab delimited with quoted column names",
SetUpScript: []string{
`CREATE TABLE Regions (
"Id" SERIAL UNIQUE NOT NULL,
"Code" VARCHAR(4) UNIQUE NOT NULL,
"Capital" VARCHAR(10) NOT NULL,
"Name" VARCHAR(255) UNIQUE NOT NULL
);`,
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY regions (\"Id\", \"Code\", \"Capital\", \"Name\") FROM stdin;\n",
CopyFromStdInFile: "tab-load-with-quoted-column-names.sql",
},
},
},
{
Name: "timestamp columns",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk timestamp primary key, ts timestamp);",
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY tbl1 FROM STDIN WITH (HEADER)",
CopyFromStdInFile: "tab-load-with-timestamp-col.sql",
},
{
Query: "select * from tbl1 order by pk;",
Expected: []sql.Row{
{"2020-12-19 19:00:00", "2021-04-04 20:00:00"},
{"2020-12-19 21:36:32.188", "2020-12-19 19:00:00"},
{"2021-04-04 20:00:00", "2020-12-19 21:36:32.188"},
},
},
},
},
{
Name: "basic csv",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY tbl1 FROM STDIN (FORMAT CSV)",
CopyFromStdInFile: "csv-load-basic-cases.sql",
},
{
Query: "select * from tbl1 where pk = 6 order by pk;",
Expected: []sql.Row{
{6, `foo
\\.
bar`, "baz"},
},
},
{
Query: "select * from tbl1 where pk = 9;",
Expected: []sql.Row{
{9, nil, "''"},
},
},
},
},
{
Name: "csv with header",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: " COPY tbl1 FROM STDIN (FORMAT CSV, HEADER TRUE);",
CopyFromStdInFile: "csv-load-with-header.sql",
},
{
Query: "select * from tbl1 where pk = 6 order by pk;",
Expected: []sql.Row{
{6, `foo
\\.
bar`, "baz"},
},
},
},
},
{
Name: "generated column",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250), c3 int generated always as (pk + 10) stored);",
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY tbl1 (pk, c1, c2) FROM STDIN (FORMAT CSV)",
CopyFromStdInFile: "csv-load-basic-cases.sql",
},
{
Query: "select * from tbl1 where pk = 6 order by pk;",
Expected: []sql.Row{
{6, `foo
\\.
bar`, "baz", 16},
},
},
{
Query: "select * from tbl1 where pk = 9;",
Expected: []sql.Row{
{9, nil, "''", 19},
},
},
},
},
{
Name: "load multiple chunks",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY tbl1 FROM STDIN (FORMAT CSV);",
CopyFromStdInFile: "csv-load-multi-chunk.sql",
},
{
Query: "select * from tbl1 where pk = 99 order by pk;",
Expected: []sql.Row{
{99, "foo", "barbazbashbarbazbashbarbazbashbarbazbashbarbazbashbarbazbashbarbazbashbarbazbashbarbazbashbarbazbashbarbazbash"},
},
},
},
},
{
Name: "load psv with headers",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key);",
"INSERT INTO test VALUES (0), (1);",
"CREATE TABLE test_info (id int, info varchar(255), test_pk int, primary key(id), foreign key (test_pk) references test(pk));",
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY test_info FROM STDIN (FORMAT CSV, HEADER TRUE, DELIMITER '|');",
CopyFromStdInFile: "psv-load.sql",
},
{
Query: "SELECT * FROM test_info order by 1;",
Expected: []sql.Row{
{4, "string for 4", 1},
{5, "string for 5", 0},
{6, "string for 6", 0},
},
},
},
},
{
Name: "csv from file",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY tbl1 FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "csv-load-basic-cases.sql")),
SkipResultsCheck: true,
},
{
Query: "select * from tbl1 where pk = 6 order by pk;",
Expected: []sql.Row{
{6, `foo
\\.
bar`, "baz"},
},
},
{
Query: "select * from tbl1 where pk = 9;",
Expected: []sql.Row{
{9, nil, "''"},
},
},
},
},
{
Name: "csv from file with column names",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY tbl1 (pk, c1, c2) FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "csv-load-basic-cases.sql")),
SkipResultsCheck: true,
},
{
Query: "select * from tbl1 where pk = 6 order by pk;",
Expected: []sql.Row{
{6, `foo
\\.
bar`, "baz"},
},
},
{
Query: "select * from tbl1 where pk = 9;",
Expected: []sql.Row{
{9, nil, "''"},
},
},
},
},
{
Name: "tab delimited with header from file",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key);",
"INSERT INTO test VALUES (0), (1);",
"CREATE TABLE test_info (id int, info varchar(255), test_pk int, primary key(id), foreign key (test_pk) references test(pk));",
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY test_info FROM '%s' WITH (HEADER)", filepath.Join(absTestDataDir, "tab-load-with-header.sql")),
},
{
Query: "SELECT * FROM test_info order by 1;",
Expected: []sql.Row{
{4, "string for 4", 1},
{5, "string for 5", 0},
{6, "string for 6", 0},
},
},
},
},
{
Name: "tab delimited with uuid values",
SetUpScript: []string{
`CREATE TABLE public.uuid_table (
id uuid NOT NULL,
name character varying NOT NULL,
second_uuid uuid DEFAULT '428d0815-d95b-4cfc-89af-9fca38585dcc'::uuid);`,
},
Assertions: []ScriptTestAssertion{
{
Query: "COPY uuid_table (id, name, second_uuid) FROM STDIN",
CopyFromStdInFile: "uuid-table.sql",
},
{
Query: "SELECT * FROM uuid_table order by id;",
Expected: []sql.Row{
{"1077f506-a6fc-4cb2-aed2-9dea9351ed9c", "Company A", "428d0815-d95b-4cfc-89af-9fca38585dcc"},
{"5e080b3a-361f-4e16-b7a4-70d4f175e283", "Company B", "428d0815-d95b-4cfc-89af-9fca38585dcc"},
},
},
},
},
{
Name: "file not found",
SetUpScript: []string{
"CREATE TABLE test (pk int primary key);",
"INSERT INTO test VALUES (0), (1);",
"CREATE TABLE test_info (id int, info varchar(255), test_pk int, primary key(id), foreign key (test_pk) references test(pk));",
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY test_info FROM '%s' WITH (HEADER)", filepath.Join(absTestDataDir, "file-not-found.sql")),
ExpectedErr: "file", // exact error message varies by platform
},
},
},
{
Name: "wrong columns",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY tbl1 (pk, c1) FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "csv-load-basic-cases.sql")),
ExpectedErr: "extra data after last expected column",
},
{
Query: fmt.Sprintf("COPY tbl1 (pk, c1, c3) FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "csv-load-basic-cases.sql")),
ExpectedErr: "Unknown column",
},
},
},
{
Name: "table not found",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY tbl2 (pk, c1) FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "csv-load-basic-cases.sql")),
ExpectedErr: "table not found: tbl2",
},
},
},
{
Name: "read only table",
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY dolt_log FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "csv-load-basic-cases.sql")),
ExpectedErr: "table doesn't support INSERT INTO",
},
},
},
{
Name: "bad data rows",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int primary key, c1 varchar(100), c2 varchar(250));",
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("COPY tbl1 (pk, c1, c2) FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "missing-columns.sql")),
ExpectedErr: "record on line 2: wrong number of fields",
},
{
Query: "select count(*) from tbl1;",
Expected: []sql.Row{{0}},
},
{
Query: fmt.Sprintf("COPY tbl1 (pk, c1, c2) FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "too-many-columns.sql")),
ExpectedErr: "record on line 6: wrong number of fields",
},
{
Query: "select count(*) from tbl1;",
Expected: []sql.Row{{0}},
},
{
Query: fmt.Sprintf("COPY tbl1 (pk, c1, c2) FROM '%s' (FORMAT CSV)", filepath.Join(absTestDataDir, "wrong-types.sql")),
ExpectedErr: "invalid input syntax for type int4",
},
{
Query: "select count(*) from tbl1;",
Expected: []sql.Row{{0}},
},
},
},
})
}
+452
View File
@@ -0,0 +1,452 @@
/// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCasts(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "CREATE CAST with function creates explicit-only cast",
SetUpScript: []string{
`CREATE TABLE cast_explicit_src (v text);`,
`CREATE TABLE cast_explicit_dst (v text, tag text);`,
`CREATE FUNCTION cast_explicit_fn(src cast_explicit_src) RETURNS cast_explicit_dst
AS $$ SELECT ROW((src).v, 'explicit')::cast_explicit_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_explicit_accept(dst cast_explicit_dst) RETURNS text
AS $$ SELECT (dst).v || ':' || (dst).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_explicit_holder (v cast_explicit_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_explicit_accept((ROW('one')::cast_explicit_src)::cast_explicit_dst);`,
Expected: []sql.Row{{"one:explicit"}},
},
{
Query: `SELECT cast_explicit_accept(ROW('one')::cast_explicit_src);`,
ExpectedErr: "does not exist",
},
{
Query: `INSERT INTO cast_explicit_holder VALUES (ROW('one')::cast_explicit_src);`,
ExpectedErr: "cast_explicit_src",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_explicit_src'
AND dst.typname = 'cast_explicit_dst';`,
Expected: []sql.Row{{"e", "f"}},
},
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
ExpectedErr: "already exists",
},
},
},
{
Name: "CREATE CAST with PL/pgSQL function creates explicit-only cast",
SetUpScript: []string{
`CREATE TABLE cast_explicit_src (v text);`,
`CREATE TABLE cast_explicit_dst (v text, tag text);`,
`CREATE FUNCTION cast_explicit_fn(src cast_explicit_src) RETURNS cast_explicit_dst AS $$ BEGIN
RETURN ROW((src).v, 'explicit')::cast_explicit_dst;
END; $$ LANGUAGE plpgsql;`,
`CREATE FUNCTION cast_explicit_accept(dst cast_explicit_dst) RETURNS text AS $$ BEGIN
RETURN (dst).v || ':' || (dst).tag;
END; $$ LANGUAGE plpgsql;`,
`CREATE TABLE cast_explicit_holder (v cast_explicit_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_explicit_accept((ROW('one')::cast_explicit_src)::cast_explicit_dst);`,
Expected: []sql.Row{{"one:explicit"}},
},
{
Query: `SELECT cast_explicit_accept(ROW('one')::cast_explicit_src);`,
ExpectedErr: "does not exist",
},
{
Query: `INSERT INTO cast_explicit_holder VALUES (ROW('one')::cast_explicit_src);`,
ExpectedErr: "cast_explicit_src",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_explicit_src'
AND dst.typname = 'cast_explicit_dst';`,
Expected: []sql.Row{{"e", "f"}},
},
{
Query: `CREATE CAST (cast_explicit_src AS cast_explicit_dst) WITH FUNCTION cast_explicit_fn(cast_explicit_src);`,
ExpectedErr: "already exists",
},
},
},
{
Name: "CREATE CAST AS ASSIGNMENT works for assignment contexts only",
SetUpScript: []string{
`CREATE TABLE cast_assignment_src (v text);`,
`CREATE TABLE cast_assignment_dst (v text, tag text);`,
`CREATE FUNCTION cast_assignment_fn(cast_assignment_src) RETURNS cast_assignment_dst
AS $$ SELECT ROW(($1).v, 'assignment')::cast_assignment_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_assignment_accept(cast_assignment_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_assignment_holder (v cast_assignment_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_assignment_src AS cast_assignment_dst) WITH FUNCTION cast_assignment_fn(cast_assignment_src) AS ASSIGNMENT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_assignment_accept((ROW('on')::cast_assignment_src)::cast_assignment_dst);`,
Expected: []sql.Row{{"on:assignment"}},
},
{
Query: `INSERT INTO cast_assignment_holder VALUES (ROW('on')::cast_assignment_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_assignment_accept(v) FROM cast_assignment_holder;`,
Expected: []sql.Row{{"on:assignment"}},
},
{
Query: `SELECT cast_assignment_accept(ROW('off')::cast_assignment_src);`,
ExpectedErr: "does not exist",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_assignment_src'
AND dst.typname = 'cast_assignment_dst';`,
Expected: []sql.Row{{"a", "f"}},
},
},
},
{
Name: "CREATE CAST AS IMPLICIT works for function resolution",
SetUpScript: []string{
`CREATE TABLE cast_implicit_src (v text);`,
`CREATE TABLE cast_implicit_dst (v text, tag text);`,
`CREATE FUNCTION cast_implicit_fn(cast_implicit_src) RETURNS cast_implicit_dst
AS $$ SELECT ROW(($1).v, 'implicit')::cast_implicit_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_implicit_accept(cast_implicit_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_implicit_holder (v cast_implicit_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_implicit_src AS cast_implicit_dst) WITH FUNCTION cast_implicit_fn(cast_implicit_src) AS IMPLICIT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_implicit_accept((ROW('x')::cast_implicit_src)::cast_implicit_dst);`,
Expected: []sql.Row{{"x:implicit"}},
},
{
Query: `SELECT cast_implicit_accept(ROW('y')::cast_implicit_src);`,
Expected: []sql.Row{{"y:implicit"}},
},
{
Query: `INSERT INTO cast_implicit_holder VALUES (ROW('z')::cast_implicit_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_implicit_accept(v) FROM cast_implicit_holder;`,
Expected: []sql.Row{{"z:implicit"}},
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_implicit_src'
AND dst.typname = 'cast_implicit_dst';`,
Expected: []sql.Row{{"i", "f"}},
},
},
},
{
Name: "CREATE CAST WITH INOUT",
SetUpScript: []string{
`CREATE TABLE cast_inout_src (v text);`,
`CREATE TABLE cast_inout_dst (v int);`,
`CREATE FUNCTION cast_inout_accept(cast_inout_dst) RETURNS text
AS $$ SELECT (($1).v)::text $$ LANGUAGE SQL;`,
`CREATE TABLE cast_inout_holder (v cast_inout_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_inout_src AS cast_inout_dst) WITH INOUT AS ASSIGNMENT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_inout_accept((ROW('42')::cast_inout_src)::cast_inout_dst);`,
Expected: []sql.Row{{"42"}},
},
{
Query: `INSERT INTO cast_inout_holder VALUES (ROW('99')::cast_inout_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_inout_accept(v) FROM cast_inout_holder;`,
Expected: []sql.Row{{"99"}},
},
{
Query: `SELECT cast_inout_accept((ROW('not_an_int')::cast_inout_src)::cast_inout_dst);`,
ExpectedErr: "invalid input syntax",
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_inout_src'
AND dst.typname = 'cast_inout_dst';`,
Expected: []sql.Row{{"a", "i"}},
},
},
},
{
Name: "CREATE CAST with three-argument function receives explicit flag",
SetUpScript: []string{
`CREATE TABLE cast_three_arg_src (v text);`,
`CREATE TABLE cast_three_arg_dst (v text, tag text);`,
`CREATE FUNCTION cast_three_arg_fn(cast_three_arg_src, integer, boolean) RETURNS cast_three_arg_dst
AS $$
SELECT ROW(
($1).v,
CASE WHEN $3 THEN 'explicit' ELSE 'implicit_or_assignment' END
)::cast_three_arg_dst
$$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_three_arg_accept(cast_three_arg_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE TABLE cast_three_arg_holder (v cast_three_arg_dst);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_three_arg_src AS cast_three_arg_dst) WITH FUNCTION cast_three_arg_fn(cast_three_arg_src, integer, boolean) AS IMPLICIT;`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_three_arg_accept((ROW('a')::cast_three_arg_src)::cast_three_arg_dst);`,
Expected: []sql.Row{{"a:explicit"}},
},
{
Query: `SELECT cast_three_arg_accept(ROW('b')::cast_three_arg_src);`,
Expected: []sql.Row{{"b:implicit_or_assignment"}},
},
{
Query: `INSERT INTO cast_three_arg_holder VALUES (ROW('c')::cast_three_arg_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_three_arg_accept(v) FROM cast_three_arg_holder;`,
Expected: []sql.Row{{"c:implicit_or_assignment"}},
},
{
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_three_arg_src'
AND dst.typname = 'cast_three_arg_dst';`,
Expected: []sql.Row{{"i", "f"}},
},
},
},
{
Name: "DROP CAST removes catalog entry and allows recreation",
SetUpScript: []string{
`CREATE TABLE cast_drop_src (v text);`,
`CREATE TABLE cast_drop_dst (v text, tag text);`,
`CREATE FUNCTION cast_drop_fn(cast_drop_src) RETURNS cast_drop_dst
AS $$ SELECT ROW(($1).v, 'drop')::cast_drop_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_drop_accept(cast_drop_dst) RETURNS text
AS $$ SELECT ($1).v || ':' || ($1).tag $$ LANGUAGE SQL;`,
`CREATE CAST (cast_drop_src AS cast_drop_dst) WITH FUNCTION cast_drop_fn(cast_drop_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT cast_drop_accept((ROW('before')::cast_drop_src)::cast_drop_dst);`,
Expected: []sql.Row{{"before:drop"}},
},
{
Query: `SELECT EXISTS (
SELECT 1
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_drop_src'
AND dst.typname = 'cast_drop_dst'
);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `DROP CAST (cast_drop_src AS cast_drop_dst);`,
Expected: []sql.Row{},
},
{
Query: `SELECT EXISTS (
SELECT 1
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_drop_src'
AND dst.typname = 'cast_drop_dst'
);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT cast_drop_accept((ROW('after')::cast_drop_src)::cast_drop_dst);`,
ExpectedErr: "cast_drop_src",
},
{
Query: `CREATE CAST (cast_drop_src AS cast_drop_dst) WITH FUNCTION cast_drop_fn(cast_drop_src);`,
Expected: []sql.Row{},
},
{
Query: `SELECT cast_drop_accept((ROW('after')::cast_drop_src)::cast_drop_dst);`,
Expected: []sql.Row{{"after:drop"}},
},
},
},
{
Name: "CREATE CAST function is invoked for NULL when function is not STRICT",
SetUpScript: []string{
`CREATE TABLE cast_null_src (v text);`,
`CREATE TABLE cast_null_dst (v text, tag text);`,
`CREATE FUNCTION cast_null_fn(cast_null_src) RETURNS cast_null_dst
AS $$ SELECT CASE
WHEN $1 IS NULL THEN ROW('saw_null', 'called')::cast_null_dst
ELSE ROW('hi', 'nonnull')::cast_null_dst
END $$ LANGUAGE SQL;`,
`CREATE CAST (cast_null_src AS cast_null_dst) WITH FUNCTION cast_null_fn(cast_null_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT ((NULL::cast_null_src)::cast_null_dst);`,
Expected: []sql.Row{{"(saw_null,called)"}},
},
{
Query: `SELECT ((ROW('hi')::cast_null_src)::cast_null_dst);`,
Expected: []sql.Row{{"(hi,nonnull)"}},
},
},
},
{
Name: "CREATE CAST STRICT function is not invoked for NULL",
SetUpScript: []string{
`CREATE TABLE cast_strict_null_src (v text);`,
`CREATE TABLE cast_strict_null_dst (v text, tag text);`,
`CREATE FUNCTION cast_strict_null_fn(cast_strict_null_src) RETURNS cast_strict_null_dst
AS $$ SELECT ROW('bad', 'called')::cast_strict_null_dst $$ LANGUAGE SQL STRICT;`,
`CREATE CAST (cast_strict_null_src AS cast_strict_null_dst) WITH FUNCTION cast_strict_null_fn(cast_strict_null_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT ((NULL::cast_strict_null_src)::cast_strict_null_dst) IS NULL;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT ((ROW('there')::cast_strict_null_src)::cast_strict_null_dst);`,
Expected: []sql.Row{{"(bad,called)"}},
},
},
},
{
Name: "CREATE CAST validation",
SetUpScript: []string{
`CREATE TABLE cast_bad_src (v text);`,
`CREATE TABLE cast_bad_dst (v text, tag text);`,
`CREATE FUNCTION cast_bad_wrong_return(cast_bad_src) RETURNS text
AS $$ SELECT ($1).v $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_wrong_source(text) RETURNS cast_bad_dst
AS $$ SELECT ROW($1, 'wrong_source')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_wrong_second(cast_bad_src, text) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'wrong_second')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_wrong_third(cast_bad_src, integer, integer) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'wrong_third')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_bad_too_many(cast_bad_src, integer, boolean, integer) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'too_many')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_good(cast_bad_src) RETURNS cast_bad_dst
AS $$ SELECT ROW(($1).v, 'good')::cast_bad_dst $$ LANGUAGE SQL;`,
`CREATE TABLE cast_without_src (v text);`,
`CREATE TABLE cast_without_dst (v text, tag text);`,
`CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_good(cast_bad_src);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_missing(cast_bad_src);`,
ExpectedErr: "does not exist",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_return(cast_bad_src);`,
ExpectedErr: "return data type",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_source(text);`,
ExpectedErr: "source data type",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_second(cast_bad_src, text);`,
ExpectedErr: "second argument",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_wrong_third(cast_bad_src, integer, integer);`,
ExpectedErr: "third argument",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_bad_too_many(cast_bad_src, integer, boolean, integer);`,
ExpectedErr: "one to three arguments",
},
{
Query: `CREATE CAST (cast_without_src AS cast_without_dst) WITHOUT FUNCTION;`,
ExpectedErr: "composite data types are not binary-compatible",
},
{
Query: `CREATE CAST (int4 AS float8) WITHOUT FUNCTION;`,
ExpectedErr: "source and target data types are not physically compatible",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_src) WITH INOUT;`,
ExpectedErr: "source data type and target data type are the same",
},
{
Query: `CREATE CAST (cast_bad_src AS cast_bad_dst) WITH FUNCTION cast_good(cast_bad_src);`,
ExpectedErr: "cast from type cast_bad_src to type cast_bad_dst already exists",
},
},
},
})
}
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import "testing"
func TestCreateDatabase(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "simple create database",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE testdb",
},
{
Query: "USE testdb",
},
},
},
{
Name: "encoding",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE testdb encoding=utf8",
},
{
Query: "USE testdb",
},
{
Query: "CREATE DATABASE testdb2 encoding=latin1",
},
{
Query: "USE testdb2",
},
{
Query: "CREATE DATABASE testdb3 encoding=notexist",
// Errors converted to warnings for now
},
},
},
{
Name: "multiple options",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE testdb OWNER=foo ENCODING=utf8",
},
{
Query: "USE testdb",
},
},
},
{
Name: "with hyphen",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE \"test-db\"",
},
{
Query: "USE \"test-db\"",
},
},
},
})
}
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"os"
"runtime"
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCreateExtension(t *testing.T) {
if runtime.GOOS == "windows" && os.Getenv("CI") != "" {
t.Skip("CI Postgres installation seems to behave weirdly, skipping for now") // TODO: look into this a bit more
}
RunScripts(t, []ScriptTest{
{
Name: "Extension Test: uuid-ossp",
SetUpScript: []string{
`CREATE EXTENSION "uuid-ossp";`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT uuid_ns_url();",
Expected: []sql.Row{{"6ba7b811-9dad-11d1-80b4-00c04fd430c8"}},
},
{
Skip: true, // This is returning different results on different platforms for some reason
Query: "SELECT uuid_generate_v3('00000000-0000-0000-0000-000000000000'::uuid, 'example text');",
Expected: []sql.Row{{"a55b875a-1bd9-31af-ac66-7d8323785c6e"}},
},
{
Skip: true, // For some reason, this returns the same result as above
Query: "SELECT uuid_generate_v3('00000000-0000-0000-0000-000000000001'::uuid, 'example text');",
Expected: []sql.Row{{"a319ab51-8e26-37c6-942f-7dd5fda5c3ef"}},
},
{
Skip: true, // Need to figure out why the result is wrong
Query: "SELECT uuid_generate_v3(uuid_ns_url(), 'example text');",
Expected: []sql.Row{{"6541262f-d622-3e35-8873-2b227591bf69"}},
},
{
Query: "SELECT uuid_nil();",
Expected: []sql.Row{{"00000000-0000-0000-0000-000000000000"}},
},
{
Query: "SELECT length(uuid_nil()::text);",
Expected: []sql.Row{{36}},
},
{
Query: "SELECT length(uuid_generate_v4()::text);",
Expected: []sql.Row{{36}},
},
{
Query: "SELECT uuid_generate_v4() = uuid_nil();",
Expected: []sql.Row{{"f"}},
},
{
Query: `WITH u1 AS (SELECT uuid_nil() AS id), u2 AS (SELECT uuid_nil() AS id) SELECT (SELECT id FROM u1) = (SELECT id FROM u2);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `WITH u1 AS (SELECT uuid_generate_v4() AS id), u2 AS (SELECT uuid_generate_v4() AS id) SELECT (SELECT id FROM u1) = (SELECT id FROM u2);`,
Expected: []sql.Row{{"f"}},
},
},
},
{
Name: "create extension uuid-ossp after setting search_path to empty",
SetUpScript: []string{
`SELECT pg_catalog.set_config('search_path', '', false);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;`,
Expected: []sql.Row{},
},
{
Query: "SELECT uuid_nil();",
// TODO: error message should be "function uuid_nil() does not exist"
ExpectedErr: `function: 'uuid_nil' not found`,
},
{
Query: "SELECT public.uuid_nil();",
Expected: []sql.Row{{"00000000-0000-0000-0000-000000000000"}},
},
},
},
{
Name: "alter table with default expr using extension function when search_path is empty",
SetUpScript: []string{
`SELECT pg_catalog.set_config('search_path', '', false);`,
`CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public;`,
`CREATE TABLE public.goals (
id uuid DEFAULT public.uuid_generate_v4() NOT NULL,
note_id uuid,
completion_timestamp timestamp without time zone,
due_date timestamp without time zone
);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `ALTER TABLE ONLY public.goals ADD CONSTRAINT goals_pkey PRIMARY KEY (id);`,
Expected: []sql.Row{},
},
},
},
})
}
File diff suppressed because it is too large Load Diff
+434
View File
@@ -0,0 +1,434 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCreateFunctionsLanguageSQL(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "unnamed parameter",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION alt_func1(int) RETURNS int LANGUAGE sql AS 'SELECT $1 + 1';`,
Expected: []sql.Row{},
},
{
Query: `SELECT alt_func1(3);`,
Expected: []sql.Row{{4}},
},
},
},
{
Name: "named parameter",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION alt_func1(x int) RETURNS int LANGUAGE sql AS 'SELECT x + 1';`,
Expected: []sql.Row{},
},
{
Query: `SELECT alt_func1(3);`,
Expected: []sql.Row{{4}},
},
{
Query: `CREATE FUNCTION sub_numbers(x int, y int) RETURNS int LANGUAGE sql AS 'SELECT y - x';`,
Expected: []sql.Row{},
},
{
Query: `SELECT sub_numbers(1, 2);`,
Expected: []sql.Row{{1}},
},
},
},
{
Name: "unknown functions",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION get_grade_description(score INT)
RETURNS TEXT
LANGUAGE SQL
AS $$
SELECT
CASE
WHEN score >= 90 THEN 'Excellent'
WHEN score >= 75 THEN 'Good'
WHEN score >= 50 THEN 'Average'
ELSE 'Fail'
END;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT get_grade_description(92);`,
Expected: []sql.Row{{"Excellent"}},
},
{
Query: `SELECT get_grade_description(65);`,
Expected: []sql.Row{{"Average"}},
},
},
},
{
Name: "nested functions",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION calculate_double_sum(x INT, y INT)
RETURNS INT
LANGUAGE SQL
AS $$
SELECT add_numbers(x, y) * 2;
$$;`,
// TODO: error message should be: function add_numbers(integer, integer) does not exist
ExpectedErr: "function: 'add_numbers' not found",
},
{
Query: `CREATE FUNCTION add_numbers(int, int) RETURNS int LANGUAGE sql AS 'SELECT $1 + $2';`,
Expected: []sql.Row{},
},
{
Query: `CREATE FUNCTION calculate_double_sum(x INT, y INT)
RETURNS INT
LANGUAGE SQL
AS $$
SELECT add_numbers(x, y) * 2;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT calculate_double_sum(1, 2);`,
Expected: []sql.Row{{6}},
},
},
},
{
Name: "function returning multiple rows",
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION gen(a int) RETURNS SETOF INT LANGUAGE SQL AS $$ SELECT generate_series(1, a) $$ STABLE;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM gen(3);`,
Expected: []sql.Row{{1}, {2}, {3}},
},
},
},
{
Name: "function with create or replace view",
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION public.sp_build_view_bathymetry_layer() RETURNS void
LANGUAGE sql
AS $$
CREATE OR REPLACE VIEW public.view_bathymetry_layer AS
SELECT 1;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT public.sp_build_view_bathymetry_layer()`,
Expected: []sql.Row{},
},
{
Query: `SELECT * from view_bathymetry_layer`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT public.sp_build_view_bathymetry_layer()`,
Expected: []sql.Row{},
},
{
Query: `SELECT * from view_bathymetry_layer`,
Expected: []sql.Row{{1}},
},
},
},
{
Name: "function with update ... returning",
SetUpScript: []string{
`CREATE TYPE public.tax_job_state AS ENUM (
'sched',
'busy',
'final',
'help'
);`,
`CREATE TABLE public.tax_job (
id bigint NOT NULL,
state public.tax_job_state NOT NULL,
created timestamp NOT NULL,
modified timestamp NOT NULL,
scheduled timestamp,
worker text,
processor text,
ext_id text,
data jsonb,
gross integer,
notes text[],
ops jsonb,
CONSTRAINT tax_job_check CHECK ((NOT ((state = 'sched'::public.tax_job_state) AND (scheduled IS NULL)))),
CONSTRAINT tax_job_check1 CHECK ((NOT ((state = 'busy'::public.tax_job_state) AND (worker IS NULL))))
);`,
`INSERT INTO tax_job (id, state, created, modified, scheduled, worker, processor, ext_id, data) VALUES (1, 'sched', '2025-05-05 05:05:05', '2025-05-05 05:05:05', '2025-05-05 05:05:05', 'worker', 'processor', 'ext_id', NULL)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION public.tax_job_take(arg_worker text) RETURNS SETOF public.tax_job
LANGUAGE sql
AS '
UPDATE
tax_job
SET
state = ''busy'',
worker = arg_worker
WHERE
state = ''sched''
AND scheduled <= CURRENT_TIMESTAMP
RETURNING
*;
';`,
Expected: []sql.Row{},
},
{
Query: `SELECT public.tax_job_take('worker')`,
Expected: []sql.Row{{`(1,busy,"2025-05-05 05:05:05","2025-05-05 05:05:05","2025-05-05 05:05:05",worker,processor,ext_id,,,,)`}},
},
{
Query: `INSERT INTO tax_job (id, state, created, modified, scheduled, worker, processor, ext_id, data) VALUES (2, 'sched', '2025-05-05 05:05:06', '2025-05-05 05:05:06', '2025-05-05 05:05:06', 'worker', 'processor', 'ext_id', NULL), (3, 'sched', '2025-05-05 05:05:07', '2025-05-05 05:05:07', '2025-05-05 05:05:07', 'worker', 'processor', 'ext_id', NULL)`,
},
{
Query: `SELECT public.tax_job_take('worker')`,
Expected: []sql.Row{
{`(2,busy,"2025-05-05 05:05:06","2025-05-05 05:05:06","2025-05-05 05:05:06",worker,processor,ext_id,,,,)`},
{`(3,busy,"2025-05-05 05:05:07","2025-05-05 05:05:07","2025-05-05 05:05:07",worker,processor,ext_id,,,,)`},
},
},
},
},
{
Name: "function with delete",
SetUpScript: []string{
`CREATE TABLE test (id bigint NOT NULL, state text NOT NULL);`,
`INSERT INTO test VALUES (1, 'sched'), (2, 'busy');`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION d(w text) RETURNS bigint
LANGUAGE sql
AS '
DELETE FROM test
WHERE
state = w
RETURNING
id;
';`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test;`,
Expected: []sql.Row{{1, "sched"}, {2, "busy"}},
},
{
Query: `SELECT d('sched');`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT * FROM test;`,
Expected: []sql.Row{{2, "busy"}},
},
},
},
{
Name: "multiple statements in function",
SetUpScript: []string{
`CREATE TABLE test (id int);`,
`INSERT INTO test VALUES (1), (2), (3);`,
`CREATE VIEW test1 AS SELECT * FROM test WHERE id = 1;`,
`CREATE VIEW test2 AS SELECT * FROM test WHERE id = 2;`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION drop_views() RETURNS void
LANGUAGE sql
AS $$
DROP VIEW test1;
DROP VIEW test2;
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test1`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT * FROM test2`,
Expected: []sql.Row{{2}},
},
{
Query: `SELECT drop_views();`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT * FROM test1`,
ExpectedErr: `not found`,
},
{
Query: `SELECT * FROM test2`,
ExpectedErr: `not found`,
},
},
},
{
Name: "function with default expression in parameter",
SetUpScript: []string{
`CREATE TABLE cp_test (a int, b text);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE OR REPLACE FUNCTION dfunc(e int, d text, f int default 100)
RETURNS int LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(e+f, d);
SELECT a FROM cp_test WHERE b = d;
$$;`,
Expected: []sql.Row{},
},
{
Query: `CREATE OR REPLACE FUNCTION dfunc(e int, f int default 100)
RETURNS int LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(e+f, 'seconddfunc');
SELECT a FROM cp_test WHERE b = 'seconddfunc';
$$;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM dfunc(10, 'Hello', 20);`,
Expected: []sql.Row{{30}},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{30, "Hello"}},
},
{
Query: `SELECT * FROM dfunc(50, 'Bye');`,
Expected: []sql.Row{{150}},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{30, "Hello"}, {150, "Bye"}},
},
{
Query: `SELECT dfunc(2, 'After');`,
Expected: []sql.Row{{102}},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{30, "Hello"}, {150, "Bye"}, {102, "After"}},
},
{
Query: `CREATE OR REPLACE FUNCTION dfunc(e int, f text default '100')
RETURNS int LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(e, f);
SELECT a FROM cp_test WHERE b = f;
$$;`,
Expected: []sql.Row{},
},
{
// TODO: the error message should be "function dfunc(integer) is not unique"
Query: `SELECT dfunc(50);`,
ExpectedErr: `does not exist`,
},
},
},
{
Name: "use sql statements in BEGIN ATOMIC ... END in sql_body",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION match_default() RETURNS jsonb
LANGUAGE sql
BEGIN ATOMIC
SELECT jsonb_build_object('k', 6, 'm', 2048, 'include_original', true, 'tokenizer', json_build_object('kind', 'ngram', 'token_length', 3), 'token_filters', json_build_array(json_build_object('kind', 'downcase'))) AS jsonb_build_object;
END;`,
Expected: []sql.Row{},
},
{
Skip: true, // TODO support json_build_object() function
Query: `SELECT public.match_default();`,
Expected: []sql.Row{{`{"k": 6, "m": 2048, "tokenizer": {"kind": "ngram", "token_length": 3}, "token_filters": [{"kind": "downcase"}], "include_original": true}`}},
},
{
Query: `CREATE FUNCTION select1() RETURNS int
LANGUAGE sql
BEGIN ATOMIC
SELECT 1;
END;`,
Expected: []sql.Row{},
},
{
Query: `SELECT select1();`,
Expected: []sql.Row{{1}},
},
},
},
{
Name: "use RETURN in BEGIN ATOMIC ... END in sql_body",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION return1() RETURNS text
LANGUAGE sql
BEGIN ATOMIC
RETURN 1::text || 'one';
END;`,
Expected: []sql.Row{},
},
{
Query: `SELECT return1();`,
Expected: []sql.Row{{"1one"}},
},
},
},
{
Name: "user-defined composite array parameter and return type",
SetUpScript: []string{
`CREATE TYPE aggtype AS (a integer, b integer, c text)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE FUNCTION aggf_trans(aggtype[],integer,integer,text) RETURNS aggtype[] AS 'select array_append($1,ROW($2,$3,$4)::aggtype)' LANGUAGE sql STRICT IMMUTABLE`,
Expected: []sql.Row{},
},
{
Query: `SELECT aggf_trans(ARRAY[]::aggtype[], 1, 2, 'hello')`,
Expected: []sql.Row{{`{"(1,2,hello)"}`}},
},
{
Query: `SELECT aggf_trans(ARRAY[ROW(1,2,'hello')::aggtype], 3, 4, 'world')`,
Expected: []sql.Row{{`{"(1,2,hello)","(3,4,world)"}`}},
},
},
},
})
}
+488
View File
@@ -0,0 +1,488 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCreateProcedureLanguagePlpgsql(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Simple example",
SetUpScript: []string{
`CREATE TABLE test (v1 INT8);`,
`CREATE PROCEDURE example(input INT8) AS $$
BEGIN
INSERT INTO test VALUES (input);
END;
$$ LANGUAGE 'plpgsql';`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CALL example(1);",
Expected: []sql.Row{},
},
{
Query: "CALL example('2');",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{
{1},
{2},
},
},
},
},
{
Name: "WHILE Label",
SetUpScript: []string{
`CREATE TABLE test (v1 INT8);`,
`CREATE PROCEDURE interpreted_while_label(input INT4) AS $$
DECLARE
counter INT4;
BEGIN
<<while_label>>
WHILE input < 1000 LOOP
input := input + 1;
counter := counter + 1;
IF counter >= 10 THEN
EXIT while_label;
END IF;
END LOOP;
INSERT INTO test VALUES (input);
END;
$$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CALL interpreted_while_label(42);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{52}},
},
},
},
{
Name: "Overloading",
SetUpScript: []string{
`CREATE TABLE test (v1 TEXT);`,
`CREATE PROCEDURE interpreted_overload(input TEXT) AS $$
DECLARE
var1 TEXT;
BEGIN
IF length(input) > 3 THEN
var1 := input || '_long';
ELSE
var1 := input;
END IF;
INSERT INTO test VALUES (var1);
END;
$$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE interpreted_overload(input INT4) AS $$
DECLARE
var1 INT4;
BEGIN
IF input > 3 THEN
var1 := -input;
ELSE
var1 := input;
END IF;
INSERT INTO test VALUES (var1::text);
END;
$$ LANGUAGE plpgsql;`},
Assertions: []ScriptTestAssertion{
{
Query: "CALL interpreted_overload('abc');",
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_overload('abcd');",
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_overload(3);",
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_overload(4);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{
{"abc"},
{"abcd_long"},
{"3"},
{"-4"},
},
},
},
},
{
Name: "Branching",
SetUpScript: []string{
`CREATE TABLE test(v1 INT4, v2 INT4);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE PROCEDURE interpreted_branch(input INT4) AS $$
BEGIN
DELETE FROM test WHERE v1 = 1;
INSERT INTO test VALUES (1, input + 100);
END;
$$ LANGUAGE plpgsql;`,
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_branch(4);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{1, 104}},
},
{
Query: "DELETE FROM test WHERE v1 = 1;",
Expected: []sql.Row{},
},
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'initial')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT dolt_checkout('-b', 'other')`,
Expected: []sql.Row{{`{0,"Switched to branch 'other'"}`}},
},
{
Query: `CREATE OR REPLACE PROCEDURE interpreted_branch(input INT4) AS $$
BEGIN
DELETE FROM test WHERE v1 = 2;
INSERT INTO test VALUES (2, input + 1000);
END;
$$ LANGUAGE plpgsql;`,
Expected: []sql.Row{},
},
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'updated func')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: "CALL interpreted_branch(56);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{2, 1056}},
},
{
Query: "DELETE FROM test WHERE v1 = 2;",
Expected: []sql.Row{},
},
{
Query: "SELECT dolt_checkout('main')",
Expected: []sql.Row{{`{0,"Switched to branch 'main'"}`}},
},
{
Query: "CALL interpreted_branch(57);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{1, 157}},
},
},
},
{
Name: "Merging No Conflict",
SetUpScript: []string{
`CREATE TABLE test(v1 INT4, v2 INT4);`,
`INSERT INTO test VALUES (1, 77);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE PROCEDURE interpreted_merging(input TEXT) AS $$
BEGIN
DELETE FROM test WHERE v1 = 2;
INSERT INTO test VALUES (2, input::int4 + 100);
END;
$$ LANGUAGE plpgsql;`,
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_merging('12');",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{1, 77}, {2, 112}},
},
{
Query: "CALL interpreted_merging(55);",
ExpectedErr: "does not exist",
},
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'initial')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT dolt_checkout('-b', 'other')`,
Expected: []sql.Row{{`{0,"Switched to branch 'other'"}`}},
},
{
Query: `CREATE PROCEDURE interpreted_merging(input INT4) AS $$
BEGIN
DELETE FROM test WHERE v1 = 3;
INSERT INTO test VALUES (3, input::int4 + 1000);
END;
$$ LANGUAGE plpgsql;`,
Expected: []sql.Row{},
},
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'another func')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT dolt_checkout('main')",
Expected: []sql.Row{{`{0,"Switched to branch 'main'"}`}},
},
{
Query: "CALL interpreted_merging(55);",
ExpectedErr: "does not exist",
},
{
Query: `CREATE OR REPLACE PROCEDURE interpreted_merging(input TEXT) AS $$
BEGIN
DELETE FROM test WHERE v1 = 2;
INSERT INTO test VALUES (2, input::int4 + 10000);
END;
$$ LANGUAGE plpgsql;`,
Expected: []sql.Row{},
},
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'updated table')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT length(dolt_merge('other')::text) = 57;",
Expected: []sql.Row{{"t"}},
},
{
Query: "CALL interpreted_merging('33');",
Expected: []sql.Row{},
},
{
Query: "CALL interpreted_merging(77);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{{1, 77}, {2, 10033}, {3, 1077}},
},
},
},
{
Name: `Procedure updates "definition" with custom body`,
SetUpScript: []string{
`CREATE TABLE test (v1 TEXT);`,
`CREATE PROCEDURE interpreted_example(input TEXT) AS $$ BEGIN INSERT INTO test VALUES ('1' || input); END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'initial')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT dolt_checkout('-b', 'other')`,
Expected: []sql.Row{{`{0,"Switched to branch 'other'"}`}},
},
{
Query: "CREATE OR REPLACE PROCEDURE interpreted_example(input TEXT) AS $$ BEGIN INSERT INTO test VALUES ('3' || input); END; $$ LANGUAGE plpgsql;",
Expected: []sql.Row{},
},
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'other')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT dolt_checkout('main')`,
Expected: []sql.Row{{`{0,"Switched to branch 'main'"}`}},
},
{
Query: "CREATE OR REPLACE PROCEDURE interpreted_example(input TEXT) AS $$ BEGIN INSERT INTO test VALUES ('2' || input); END; $$ LANGUAGE plpgsql;",
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM dolt_status;`,
Expected: []sql.Row{
{"public.interpreted_example(text)", "f", "modified"},
},
},
{
Query: `SELECT dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: "SELECT length(dolt_commit('-m', 'next')::text) = 34;",
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT dolt_merge('other');`,
Expected: []sql.Row{
{`{"",0,1,"conflicts found"}`},
},
},
{
Query: `SELECT * FROM dolt_conflicts;`,
Expected: []sql.Row{
{"public.interpreted_example(text)", Numeric("1")},
},
},
{
Query: `SELECT base_value, our_value, our_diff_type, their_value, their_diff_type, dolt_conflict_id FROM "dolt_conflicts_interpreted_example(text)";`,
Expected: []sql.Row{
{"BEGIN INSERT INTO test VALUES ('1' || input); END;", "BEGIN INSERT INTO test VALUES ('2' || input); END;", "modified", "BEGIN INSERT INTO test VALUES ('3' || input); END;", "modified", "definition"},
},
},
{
Query: `UPDATE "dolt_conflicts_interpreted_example(text)" SET our_value = 'BEGIN INSERT INTO test VALUES (''7'' || input); END;' WHERE dolt_conflict_id = 'definition';`,
Expected: []sql.Row{},
},
{
Query: `DELETE FROM "dolt_conflicts_interpreted_example(text)" WHERE dolt_conflict_id = 'definition';`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM "dolt_conflicts_interpreted_example(text)";`,
ExpectedErr: `table not found`,
},
{
Query: "CALL interpreted_example('12');",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM test;",
Expected: []sql.Row{
{"712"},
},
},
},
},
{
Name: "DECLARE variable with default value of literal value or parameter reference",
SetUpScript: []string{
`CREATE TABLE t (a int, b text);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE OR REPLACE PROCEDURE m (x text, y text, e int) LANGUAGE plpgsql AS $$
declare
xx text := x;
yy text := y;
zz int := e;
begin
insert into t values (zz, xx || yy);
end
$$;`,
},
{
Query: "CALL m('a', 'B', 1);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM t",
Expected: []sql.Row{{1, "aB"}},
},
},
},
{
Name: "use nested block statements and call statement in procedure body",
SetUpScript: []string{
`CREATE TABLE tbl (a int, b text);`,
`CREATE PROCEDURE add_value(IN a int, IN b text)
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO tbl VALUES (a, b);
END;
$$;`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE PROCEDURE check_and_add(IN i int, IN t text)
LANGUAGE plpgsql
AS $$
DECLARE d text := t;
BEGIN
IF LENGTH(t) < 6 THEN
d = t || ' is too short';
END IF;
BEGIN
CALL add_value(i, d);
END;
END;
$$;`,
},
{
Query: "CALL check_and_add(1, 'hi');",
Expected: []sql.Row{},
},
{
Query: "CALL check_and_add(3, 'hellooo');",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM tbl",
Expected: []sql.Row{{1, "hi is too short"}, {3, "hellooo"}},
},
},
},
})
}
+130
View File
@@ -0,0 +1,130 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCreateProcedureLanguageSql(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "procedure with insert returning",
SetUpScript: []string{
`CREATE TABLE public.games (
id bigint NOT NULL,
game_id character varying(4) NOT NULL,
host_connection_id character varying(50) NOT NULL
);`,
`CREATE SEQUENCE public.games_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;`,
`ALTER SEQUENCE public.games_id_seq OWNED BY public.games.id;`,
`ALTER TABLE ONLY public.games ALTER COLUMN id SET DEFAULT nextval('public.games_id_seq'::regclass);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE PROCEDURE public.add(INOUT new_host_connection_id character varying)
LANGUAGE sql
AS $$
INSERT INTO public.games (
game_id,
host_connection_id
)
VALUES (2222, new_host_connection_id)
RETURNING game_id;
$$;`,
Expected: []sql.Row{},
},
{
SkipResultsCheck: true, // TODO: need fix for returning results
Query: `CALL add('f')`,
Expected: []sql.Row{{"2222"}},
},
{
Query: `SELECT id, game_id, host_connection_id FROM games`,
Expected: []sql.Row{{1, "2222", "f"}},
},
{
Query: `CREATE PROCEDURE public.create_game(INOUT new_host_connection_id character varying)
LANGUAGE sql
AS $$
WITH new_game_id_holder (new_game_id) AS (
SELECT n.random_number
FROM (
SELECT LPAD(FLOOR(random() * 10000)::varchar, 4, '0') AS random_number
FROM generate_series(1, (SELECT COUNT(*) FROM public.games) + 10)
) AS n
LEFT OUTER JOIN
public.games AS g on g.game_id = n.random_number
WHERE g.id IS NULL
LIMIT 1
)
INSERT INTO public.games (
game_id,
host_connection_id
)
VALUES (
(SELECT new_game_id FROM new_game_id_holder),
new_host_connection_id
)
RETURNING game_id;
$$;`,
Expected: []sql.Row{},
},
{
Query: `CALL create_game('d')`,
},
{
Query: `SELECT id, host_connection_id FROM games`,
Expected: []sql.Row{{1, "f"}, {2, "d"}},
},
},
},
{
Name: "procedure with default expression in parameter",
SetUpScript: []string{
`CREATE TABLE cp_test (a int, b text);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE OR REPLACE PROCEDURE ptest5(a int, b text, c int default 100)
LANGUAGE SQL
AS $$
INSERT INTO cp_test VALUES(a, b);
INSERT INTO cp_test VALUES(c, b);
$$;`,
Expected: []sql.Row{},
},
{
Query: `CALL ptest5(10, 'Hello', 20);`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{10, "Hello"}, {20, "Hello"}},
},
{
Query: `CALL ptest5(50, 'Bye');`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM cp_test`,
Expected: []sql.Row{{10, "Hello"}, {20, "Hello"}, {50, "Bye"}, {100, "Bye"}},
},
},
},
})
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"context"
"fmt"
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/jackc/pgx/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
dserver "github.com/dolthub/doltgresql/server"
"github.com/dolthub/doltgresql/servercfg"
"github.com/dolthub/doltgresql/servercfg/cfgdetails"
)
// TestCreateSchemaWithNonExistentDatabase tests that connecting to a non-existent
// database fails with the appropriate error at the connection level.
// This is the PostgreSQL-compliant behavior where database validation happens
// during connection establishment.
//
// NOTE: This test cannot use RunScripts because RunScripts auto-creates the
// database before running assertions. We need to test the connection-level
// failure when connecting to a non-existent database.
//
// See: https://github.com/dolthub/doltgresql/issues/1863
func TestCreateSchemaWithNonExistentDatabase(t *testing.T) {
port, err := sql.GetEmptyPort()
require.NoError(t, err)
// Start server using the same pattern as CreateServerWithPort
controller, err := dserver.RunInMemory(
&servercfg.DoltgresConfig{
DoltgresConfig: cfgdetails.DoltgresConfig{
ListenerConfig: &cfgdetails.DoltgresListenerConfig{
PortNumber: &port,
HostStr: &serverHost,
},
LogLevelStr: &testServerLogLevel,
},
}, dserver.NewListener,
)
require.NoError(t, err)
defer func() {
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()
ctx := context.Background()
t.Run(
"connection to non-existent database fails", func(t *testing.T) {
// Attempt to connect to a database that doesn't exist
connStr := fmt.Sprintf(
"postgres://postgres:password@%s:%d/nonexistent_db?sslmode=disable",
serverHost,
port,
)
_, err := pgx.Connect(ctx, connStr)
require.Error(t, err, "connection should fail when database doesn't exist")
assert.Contains(
t, err.Error(), "does not exist",
"expected 'does not exist' error, got: %v", err,
)
},
)
t.Run(
"connection to existing database succeeds", func(t *testing.T) {
// Verify that connecting to the default "postgres" database works
connStr := fmt.Sprintf("postgres://postgres:password@%s:%d/postgres?sslmode=disable", serverHost, port)
conn, err := pgx.Connect(ctx, connStr)
require.NoError(t, err)
defer conn.Close(ctx)
// Verify we can create a schema on the valid database
_, err = conn.Exec(ctx, "CREATE SCHEMA test_schema_1863")
require.NoError(t, err)
// Verify the schema was created
rows, err := conn.Query(
ctx,
"SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'test_schema_1863'",
)
require.NoError(t, err)
defer rows.Close()
require.True(t, rows.Next(), "expected to find test_schema_1863")
},
)
}
+550
View File
@@ -0,0 +1,550 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/types"
)
func TestCreateTable(t *testing.T) {
RunScripts(t, []ScriptTest{
{
// https://github.com/dolthub/doltgresql/issues/2580
Name: "create table with UTF8 identifiers",
Assertions: []ScriptTestAssertion{
{
Query: `CREATE TABLE foo😏(data🍆 TEXT);`,
Expected: []sql.Row{},
},
{
Query: `CREATE INDEX idx🍤 ON foo😏(data🍆);`,
Expected: []sql.Row{},
},
{
Query: `Insert into foo😏 (data🍆) VALUES ('foo');`,
Expected: []sql.Row{},
},
{
Query: `SELECT data🍆 FROM foo😏;`,
Expected: []sql.Row{{"foo"}},
},
},
},
{
Name: "create table with primary key",
Assertions: []ScriptTestAssertion{
{
// TODO: we don't currently have a way to check for warnings in these tests, but this query was incorrectly
// producing a warning. Would be nice to assert no warnings on most queries.
Query: "create table employees (" +
" id int8," +
" last_name text," +
" first_name text," +
" primary key(id));",
},
{
Query: "insert into employees (id, last_name, first_name) values (1, 'Doe', 'John');",
},
{
Query: "select * from employees;",
Expected: []sql.Row{
{1, "Doe", "John"},
},
},
{
// Test that the PK constraint shows up in the information schema
Query: "SELECT conname FROM pg_constraint WHERE conrelid = 'employees'::regclass AND contype = 'p';",
Expected: []sql.Row{{"employees_pkey"}},
},
{
Query: "ALTER TABLE employees DROP CONSTRAINT employees_pkey;",
Expected: []sql.Row{},
},
},
},
{
// TODO: We don't currently support storing a custom name for a primary key constraint.
Skip: true,
Name: "create table with primary key, using custom constraint name",
SetUpScript: []string{
"CREATE TABLE users (id SERIAL, name TEXT, CONSTRAINT users_primary_key PRIMARY KEY (id));",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT conname FROM pg_constraint WHERE conrelid = 'users'::regclass AND contype = 'p';",
Expected: []sql.Row{{"users_primary_key"}},
},
{
Query: "ALTER TABLE users DROP CONSTRAINT users_primary_key;",
Expected: []sql.Row{{types.NewOkResult(0)}},
},
},
},
{
Name: "Create table with column default expression using function",
Assertions: []ScriptTestAssertion{
{
// Test with a function in the column default expression
Query: "create table t1 (pk int primary key, c1 TEXT default length('Hello World!'));",
Expected: []sql.Row{},
},
{
Query: "insert into t1(pk) values (1);",
Expected: []sql.Row{},
},
{
Query: "select * from t1;",
Expected: []sql.Row{{1, "12"}},
},
},
},
{
Name: "Create table with table check constraint",
Assertions: []ScriptTestAssertion{
{
Query: `CREATE TABLE products (name text, price numeric, discounted_price numeric, CHECK (price > discounted_price));`,
Expected: []sql.Row{},
},
{
Query: "insert into products values ('apple', 1.20, 0.80);",
Expected: []sql.Row{},
},
{
// TODO: the correct error message: `new row for relation "products" violates check constraint "products_chk_rqcthh8j"`
Query: "insert into products values ('peach', 1.20, 1.80);",
ExpectedErr: `Check constraint "products_chk_`,
},
{
Query: "select * from products;",
Expected: []sql.Row{{"apple", Numeric("1.20"), Numeric("0.80")}},
},
},
},
{
Name: "Create table with column check constraint",
Assertions: []ScriptTestAssertion{
{
Query: "create table mytbl (pk int, v1 int constraint v1constraint check (v1 < 100));",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values (1, 20);",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values (2, 200);",
ExpectedErr: `Check constraint "v1constraint" violated`,
},
{
Query: "select * from mytbl;",
Expected: []sql.Row{{1, 20}},
},
},
},
{
Name: "check constraint with a function",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE TABLE mytbl (a text CHECK (length(a) > 2) PRIMARY KEY, b text);",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values ('abc', 'def');",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values ('de', 'abc');",
ExpectedErr: `Check constraint "mytbl_chk_`,
},
{
Query: "select * from mytbl;",
Expected: []sql.Row{{"abc", "def"}},
},
},
},
{
Skip: true, // TODO: vitess does not support multiple check constraint on a single column
Name: "Create table with multiple check constraints on a single column",
Assertions: []ScriptTestAssertion{
{
Query: "create table mytbl (pk int, v1 int constraint v1constraint check (v1 < 100) check (v1 > 10));",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values (1, 20);",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values (2, 200);",
ExpectedErr: `Check constraint "v1constraint" violated`,
},
{
Query: "insert into mytbl values (3, 5);",
ExpectedErr: `Check constraint "mytbl_chk_`,
},
{
Query: "select * from mytbl;",
Expected: []sql.Row{{1, 20}},
},
},
},
{
Name: "Create table with a check constraints on a single column and a table check constraint",
Assertions: []ScriptTestAssertion{
{
Query: "create table mytbl (pk int, v1 int constraint v1constraint check (v1 < 100), check (v1 > 10));",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values (1, 20);",
Expected: []sql.Row{},
},
{
Query: "insert into mytbl values (2, 200);",
ExpectedErr: `Check constraint "v1constraint" violated`,
},
{
Query: "insert into mytbl values (3, 5);",
ExpectedErr: `Check constraint "mytbl_chk_`,
},
{
Query: "select * from mytbl;",
Expected: []sql.Row{{1, 20}},
},
},
},
{
Name: "create table with generated column",
SetUpScript: []string{
"create table t1 (a int primary key, b int, c int generated always as (a + b) stored);",
"insert into t1 (a, b) values (1, 2);",
"create table t2 (a int primary key, b int, c int generated always as (b * 10) stored);",
"insert into t2 (a, b) values (1, 2);",
},
Assertions: []ScriptTestAssertion{
{
Query: "select * from t1;",
Expected: []sql.Row{{1, 2, 3}},
},
{
Query: "select * from t2;",
Expected: []sql.Row{{1, 2, 20}},
},
},
},
{
Name: "create table with function in generated column",
SetUpScript: []string{
"create table t1 (a varchar(10) primary key, b varchar(10), c varchar(20) generated always as (concat(a,b)) stored);",
},
Assertions: []ScriptTestAssertion{
{
Query: "insert into t1 (a, b) values ('foo', 'bar');",
},
{
Query: "select * from t1;",
Expected: []sql.Row{{"foo", "bar", "foobar"}},
},
},
},
{
Name: "generated column with complex expression",
SetUpScript: []string{
`create table t1 (a varchar(10) primary key,
b varchar(20) generated always as
((
("substring"(TRIM(BOTH FROM a), '([^ ]+)$'::text) || ' '::text)
|| "substring"(TRIM(BOTH FROM a), '^([^ ]+)'::text)
)) stored
);`,
},
Assertions: []ScriptTestAssertion{
{
Query: "insert into t1 (a) values (' foo ');",
},
{
Query: "select * from t1;",
Expected: []sql.Row{{" foo ", "foo foo"}},
},
},
},
{
Name: "generated column with reference to another column",
SetUpScript: []string{
`create table t1 (
a varchar(10) primary key,
b varchar(20),
b_not_null bool generated always as ((b is not null)) stored
);`,
"insert into t1 (a, b) values ('foo', 'bar');",
"insert into t1 (a) values ('foo2');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select * from t1 order by a;",
Expected: []sql.Row{
{"foo", "bar", "t"},
{"foo2", nil, "f"},
},
},
},
},
{
Name: "generated column with space in column name",
SetUpScript: []string{
`create table t1 (
a varchar(10) primary key,
"b 2" varchar(20),
b_not_null bool generated always as (("b 2" is not null)) stored
);`,
`insert into t1 (a, "b 2") values ('foo', 'bar');`,
"insert into t1 (a) values ('foo2');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select * from t1 order by a;",
Expected: []sql.Row{
{"foo", "bar", "t"},
{"foo2", nil, "f"},
},
},
},
},
{
Name: "primary key GENERATED ALWAYS AS IDENTITY",
SetUpScript: []string{
`create table t1 (
a BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
b varchar(100)
);`,
},
Assertions: []ScriptTestAssertion{
{
Query: "insert into t1 (b) values ('foo') returning a;",
Expected: []sql.Row{
{1},
},
},
{
Query: "insert into t1 (a, b) values (2, 'foo') returning a;",
ExpectedErr: "The value specified for generated column \"a\" in table \"t1\" is not allowed",
},
},
},
{
Name: "create table with default value",
SetUpScript: []string{
"create table t1 (a varchar(10) primary key, b varchar(10) default (concat('foo', 'bar')));",
"insert into t1 (a) values ('abc');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select * from t1;",
Expected: []sql.Row{{"abc", "foobar"}},
},
},
},
{
Name: "create table with collation",
SetUpScript: []string{
`CREATE TABLE collate_test1 (
a int,
b text COLLATE "en-x-icu" NOT NULL
)`,
"insert into collate_test1 (a, b) values (1, 'foo');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select * from collate_test1;",
Expected: []sql.Row{{1, "foo"}},
},
},
},
{
Name: "inline comments",
Assertions: []ScriptTestAssertion{
{
Query: `CREATE TABLE inline_comments (
a int,
b int,
c int, -- comment on end of line
CONSTRAINT check_b CHECK (b IS NULL OR b = 'a'),
CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
);`,
},
{
Query: `CREATE TABLE block_comments (
a int,
b /* block comment */ /* one more thing */ int, -- comment on end of line
c int, -- comment on end of line /* block comment */
CONSTRAINT check_b CHECK (b IS NULL OR b = 'a'),
CONSTRAINT check_a CHECK (a IS NOT NULL AND a = 7)
);`,
},
},
},
{
Name: "create temporary table with serial column",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE TEMP TABLE temp (id serial primary key)",
Expected: []sql.Row{},
},
},
},
{
Name: "table with check constraint with ANY expression",
SetUpScript: []string{
`CREATE TABLE location (
id integer NOT NULL,
name character varying(100) NOT NULL,
type character varying(100),
CONSTRAINT location_type_check CHECK (((type)::text = ANY ((ARRAY['Внутренни'::character varying, 'Покупатель'::character varying, 'Поставщик'::character varying])::text[])))
);`,
},
Assertions: []ScriptTestAssertion{
{
Query: "insert into location values (1, 'Склад Москва', 'Внутренни'), (2, 'Склад Спб', null);",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM location;",
Expected: []sql.Row{
{1, "Склад Москва", "Внутренни"},
{2, "Склад Спб", nil},
},
},
},
},
{
Name: "Table names must be unique across all relation types",
SetUpScript: []string{
"CREATE TABLE existing_tbl (pk int PRIMARY KEY, v1 int);",
"CREATE SEQUENCE seq1;",
"CREATE VIEW view1 AS SELECT pk FROM existing_tbl;",
"CREATE INDEX idx1 ON existing_tbl (v1);",
},
Assertions: []ScriptTestAssertion{
{
Query: "CREATE TABLE existing_tbl (c1 int);",
ExpectedErr: `relation "existing_tbl" already exists`,
},
{
Query: "CREATE TABLE IF NOT EXISTS existing_tbl (c1 int);",
Expected: []sql.Row{},
},
{
Query: "CREATE TABLE seq1 (c1 int);",
ExpectedErr: `relation "seq1" already exists`,
},
{
Query: "CREATE TABLE IF NOT EXISTS seq1 (c1 int);",
Expected: []sql.Row{},
},
{
Query: "CREATE TABLE view1 (c1 int);",
ExpectedErr: `relation "view1" already exists`,
},
{
Query: "CREATE TABLE IF NOT EXISTS view1 (c1 int);",
Expected: []sql.Row{},
},
{
Query: "CREATE TABLE idx1 (c1 int);",
ExpectedErr: `relation "idx1" already exists`,
},
{
Query: "CREATE TABLE IF NOT EXISTS idx1 (c1 int);",
Expected: []sql.Row{},
},
},
},
})
}
func TestCreateTableInherit(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Create table with inheritance",
SetUpScript: []string{
"create table t1 (a int);",
"create table t2 (b int);",
"create table t3 (c int);",
"create table t11 (a int);",
},
Assertions: []ScriptTestAssertion{
{
Query: "create table t4 (d int) inherits (t1, t2, t3);",
Expected: []sql.Row{},
},
{
Query: "insert into t4(a, b, c, d) values (1, 2, 3, 4);",
Expected: []sql.Row{},
},
{
Query: "select * from t4;",
Expected: []sql.Row{
{1, 2, 3, 4},
},
},
{
Query: "create table t111 () inherits (t1, t11);",
Expected: []sql.Row{},
},
{
Query: "insert into t111(a) values (1);",
Expected: []sql.Row{},
},
{
Query: "select * from t111;",
Expected: []sql.Row{
{1},
},
},
{
Query: "create table t1t1 (a int) inherits (t1);",
Expected: []sql.Row{},
},
{
Query: "insert into t1t1(a) values (1);",
Expected: []sql.Row{},
},
{
Query: "select * from t1t1;",
Expected: []sql.Row{
{1},
},
},
{
Query: "create table TT1t1 (A int) inherits (t1);",
Expected: []sql.Row{},
},
{
Query: "insert into TT1t1(a) values (1);",
Expected: []sql.Row{},
},
{
Query: "select * from TT1t1;",
Expected: []sql.Row{
{1},
},
},
},
},
})
}
+323
View File
@@ -0,0 +1,323 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestCreateViewStatements(t *testing.T) {
RunScripts(t, createViewStmts)
}
var createViewStmts = []ScriptTest{
{
Name: "basic create view statements",
SetUpScript: []string{
"create table t1 (pk int);",
"insert into t1 values (1), (2), (3), (1);",
},
Assertions: []ScriptTestAssertion{
{
Query: "create view v as select * from t1 order by pk;",
Expected: []sql.Row{},
},
{
Query: "select * from v order by pk;",
Expected: []sql.Row{{1}, {1}, {2}, {3}},
},
},
},
{
Name: "views on different schemas",
SetUpScript: []string{
"CREATE SCHEMA testschema;",
"SET search_path TO testschema;",
"CREATE TABLE testing (pk INT primary key, v2 TEXT);",
"INSERT INTO testing VALUES (1,'a'), (2,'b'), (3,'c');",
"CREATE VIEW testview AS SELECT * FROM testing;",
"CREATE SCHEMA myschema;",
"SET search_path TO myschema;",
"CREATE TABLE mytable (pk INT primary key, v1 INT);",
"INSERT INTO mytable VALUES (1,4), (2,5), (3,6);",
"CREATE VIEW myview AS SELECT * FROM mytable;",
},
Assertions: []ScriptTestAssertion{
{
Query: "SHOW search_path;",
Expected: []sql.Row{{"myschema"}},
},
{
Query: "select v1 from myview order by pk;",
Expected: []sql.Row{{4}, {5}, {6}},
},
{
Query: "select v2 from testview order by pk;",
ExpectedErr: "table not found: testview",
},
{
Query: "select v2 from testschema.testview order by pk;",
Expected: []sql.Row{{"a"}, {"b"}, {"c"}},
},
{
Query: "select name from dolt_schemas;",
Expected: []sql.Row{{"myview"}},
},
{
Query: "SET search_path = 'testschema';",
},
{
Query: "SHOW search_path;",
Expected: []sql.Row{{"testschema"}},
},
{
Query: "select * from myview order by pk; /* err */",
ExpectedErr: "table not found: myview",
},
{
Query: "select v1 from myschema.myview order by pk;",
Expected: []sql.Row{{4}, {5}, {6}},
},
{
Query: "select v2 from testview order by pk;",
Expected: []sql.Row{{"a"}, {"b"}, {"c"}},
},
{
Query: "select name from dolt_schemas;",
Expected: []sql.Row{{"testview"}},
},
{
Query: "SET search_path = testschema, myschema;",
Expected: []sql.Row{},
},
{
Query: "SHOW search_path;",
Expected: []sql.Row{{"testschema, myschema"}},
},
{
Skip: true, // TODO: Should be able to resolve views from all schema in search_path
Query: "select v1 from myview order by pk;",
Expected: []sql.Row{{4}, {5}, {6}},
},
{
Query: "select v2 from testview order by pk;",
Expected: []sql.Row{{"a"}, {"b"}, {"c"}},
},
{
Skip: true, // TODO: Should be able to resolve views from all schema in search_path
Query: "select name from dolt_schemas;",
Expected: []sql.Row{{"testview"}, {"myview"}},
},
},
},
{
Name: "create view from view",
SetUpScript: []string{
"create table t1 (pk int);",
"insert into t1 values (1), (2), (3), (1);",
"create view v as select * from t1 where pk > 1;",
},
Assertions: []ScriptTestAssertion{
{
Query: "create view v1 as select * from v order by pk;",
Expected: []sql.Row{},
},
{
Query: "select * from v1 order by pk;",
Expected: []sql.Row{{2}, {3}},
},
},
},
{
Name: "view with expression name",
SetUpScript: []string{
"create view v as select 2+2",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT * from v;",
Expected: []sql.Row{{4}},
},
},
},
{
Name: "view with column names",
SetUpScript: []string{
`CREATE TABLE xy (x int primary key, y int);`,
`insert into xy values (1, 4), (4, 9)`,
},
Assertions: []ScriptTestAssertion{
{
Query: "create view v_today(today) as select 2",
Expected: []sql.Row{},
},
{
Query: "CREATE VIEW xyv (u,v) AS SELECT * from xy",
Expected: []sql.Row{},
},
{
Query: "SELECT v from xyv;",
Expected: []sql.Row{{4}, {9}},
},
{
Query: "SELECT today from v_today;",
Expected: []sql.Row{{2}},
},
},
},
{
Skip: true, // TODO: getting subquery alias not supported error
Name: "nested view",
SetUpScript: []string{
"create table t1 (pk int);",
"insert into t1 values (1), (2), (3), (4);",
},
Assertions: []ScriptTestAssertion{
{
Query: "create view unionView as (select * from t1 order by pk desc limit 1) union all (select * from t1 order by pk limit 1)",
Expected: []sql.Row{},
},
{
Query: "select * from unionView order by pk;",
Expected: []sql.Row{{1}, {4}},
},
},
},
{
Name: "cast (postgres-specific syntax)",
SetUpScript: []string{
"create table t1 (pk int);",
"insert into t1 values (1), (2), (3), (4);",
},
Assertions: []ScriptTestAssertion{
{
Query: "CREATE VIEW v AS SELECT pk::INT2 FROM t1 ORDER BY pk;",
Expected: []sql.Row{},
},
{
Query: "select * from v order by pk;",
Expected: []sql.Row{{1}, {2}, {3}, {4}},
},
{
Query: "CREATE VIEW v_text AS SELECT pk::int2, (pk)::text AS pk_text FROM t1;",
Expected: []sql.Row{},
},
{
Query: "select pk_text from v_text order by pk;",
Expected: []sql.Row{{"1"}, {"2"}, {"3"}, {"4"}},
},
},
},
{
Name: "not yet supported create view queries",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE TEMPORARY VIEW v AS SELECT 1;",
Skip: true,
},
{
Query: "CREATE RECURSIVE VIEW v AS SELECT 1;",
Skip: true,
},
{
Query: "CREATE VIEW v AS SELECT 1 WITH LOCAL CHECK OPTION;",
Skip: true,
},
{
Query: "CREATE VIEW v WITH (check_option = 'local') AS SELECT 1;",
Skip: true,
},
{
Query: "CREATE VIEW v WITH (security_barrier = true) AS SELECT 1;",
Skip: true,
},
},
},
{
Name: "create view with CTE",
SetUpScript: []string{
"CREATE TABLE public.t1 (id integer NOT NULL);",
},
Assertions: []ScriptTestAssertion{
{
Query: `create view public.v1 as with table1 as (select * from t1) select id from table1;`,
Expected: []sql.Row{},
},
},
},
{
Name: "create view with custom type in its select statement",
SetUpScript: []string{
"CREATE TYPE e AS ENUM ('sched', 'busy', 'final', 'help');",
"CREATE TABLE t (id integer NOT NULL, t e);",
"INSERT INTO t VALUES (1, 'busy'), (2, 'final'), (3, 'busy'), (4, 'help');",
},
Assertions: []ScriptTestAssertion{
{
Query: `create view v as select * from t where (t = 'busy'::e);`,
Expected: []sql.Row{},
},
{
Query: `select * from v;`,
Expected: []sql.Row{{1, "busy"}, {3, "busy"}},
},
},
},
{
Name: "View names must be unique across all relation types",
SetUpScript: []string{
"CREATE TABLE tbl1 (pk int PRIMARY KEY, v1 int);",
"CREATE SEQUENCE seq1;",
"CREATE VIEW existing_view AS SELECT pk FROM tbl1;",
"CREATE INDEX idx1 ON tbl1 (v1);",
},
Assertions: []ScriptTestAssertion{
{
Query: "CREATE VIEW tbl1 AS SELECT pk FROM tbl1;",
ExpectedErr: `relation "tbl1" already exists`,
},
{
Query: "CREATE OR REPLACE VIEW tbl1 AS SELECT pk FROM tbl1;",
ExpectedErr: `"tbl1" is not a view`,
},
{
Query: "CREATE VIEW seq1 AS SELECT pk FROM tbl1;",
ExpectedErr: `relation "seq1" already exists`,
},
{
Query: "CREATE OR REPLACE VIEW seq1 AS SELECT pk FROM tbl1;",
ExpectedErr: `"seq1" is not a view`,
},
{
Query: "CREATE VIEW existing_view AS SELECT pk FROM tbl1;",
ExpectedErr: `relation "existing_view" already exists`,
},
{
Query: "CREATE OR REPLACE VIEW existing_view AS SELECT pk FROM tbl1;",
Expected: []sql.Row{},
},
{
Query: "CREATE VIEW idx1 AS SELECT pk FROM tbl1;",
ExpectedErr: `relation "idx1" already exists`,
},
{
Query: "CREATE OR REPLACE VIEW idx1 AS SELECT pk FROM tbl1;",
ExpectedErr: `"idx1" is not a view`,
},
},
},
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestDelete(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "simple delete",
SetUpScript: []string{
"CREATE TABLE t123 (id int primary key, c1 varchar(100));",
"INSERT INTO t123 VALUES (1, 'one'), (2, 'two');",
},
Assertions: []ScriptTestAssertion{
{
Query: "DELETE FROM t123 where id = 1;",
Expected: []sql.Row{},
},
{
Query: "SELECT * FROM t123;",
Expected: []sql.Row{{2, "two"}},
},
},
},
{
Name: "delete returning",
SetUpScript: []string{
"CREATE TABLE t123 (id int primary key, c1 varchar(100));",
"INSERT INTO t123 VALUES (1, 'one'), (2, 'two'), (3, 'three');",
},
Assertions: []ScriptTestAssertion{
{
Query: "DELETE FROM t123 where id = 1 RETURNING id, c1;",
Expected: []sql.Row{{1, "one"}},
},
{
// Test a DELETE with no filter, to test that we don't convert
// to a TRUNCATE operation
Query: "DELETE FROM t123 RETURNING *;",
Expected: []sql.Row{{2, "two"}, {3, "three"}},
},
{
Query: "SELECT * FROM t123;",
Expected: []sql.Row{},
},
},
},
})
}
+798
View File
@@ -0,0 +1,798 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"os"
"path/filepath"
"runtime"
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/stretchr/testify/require"
)
// localBackupUrl returns a file:// URL for a subdirectory named |name| inside the test's
// auto-cleaned TempDir. Each call produces a unique parent directory, so two calls with the same
// |name| never collide. The directory is automatically removed when the test ends.
func localBackupUrl(t *testing.T, name string) string {
t.Helper()
return "file://" + filepath.ToSlash(filepath.Join(t.TempDir(), name))
}
// runBackupTest runs |script| inside a dedicated, freshly-started on-disk Doltgres server and
// tears the server down when the subtest completes.
//
// A new server is started for every call so that each backup/restore test begins with a clean
// database state. Without this isolation, objects created (or restored) in one test could leak
// into subsequent tests, producing false passes or false failures. The server runs against a
// temporary on-disk directory (via CreateServerLocalWithPort) because dolt_backup requires a real
// NBS chunk store — in-memory storage cannot be synced to a file:// backup URL.
//
// Callers must pre-compute any backup URLs with localBackupUrl(t, …) so the directories live
// inside the test's auto-cleaned TempDir rather than a persistent system path.
func runBackupTest(t *testing.T, script ScriptTest) {
t.Helper()
t.Run(script.Name, func(t *testing.T) {
t.Helper()
port, err := sql.GetEmptyPort()
require.NoError(t, err)
ctx, conn, controller := CreateServerLocalWithPort(t, "postgres", port)
defer func() {
conn.Close(ctx)
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()
runScript(t, ctx, script, conn, true)
})
}
func TestDoltBackup(t *testing.T) {
backupUrl := localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "sync-url restore preserves data and commit history",
SetUpScript: []string{
"create table items (id int primary key, label text not null);",
"insert into items values (1, 'apple'), (2, 'banana');",
"select dolt_commit('-Am', 'first commit: add apple and banana');",
"insert into items values (3, 'cherry');",
"select dolt_commit('-Am', 'second commit: add cherry');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_syncurl');", backupUrl),
"USE restored_syncurl",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, label from items order by id;",
Expected: []sql.Row{{1, "apple"}, {2, "banana"}, {3, "cherry"}},
},
{
// 4 commits: Initialize + CREATE DATABASE + 2 user commits.
Query: "select count(*) from dolt.commits;",
Expected: []sql.Row{{int64(4)}},
},
{
Query: "select message from dolt.commits order by date;",
Expected: []sql.Row{{"Initialize data repository"}, {"CREATE DATABASE"}, {"first commit: add apple and banana"}, {"second commit: add cherry"}},
},
{
// NOT NULL constraint on label must survive restore.
Query: "select column_name, is_nullable from information_schema.columns where table_name = 'items' and table_schema = 'public' order by column_name;",
Expected: []sql.Row{{"id", "NO"}, {"label", "NO"}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "named backup sync restore preserves data and commit history",
SetUpScript: []string{
"create table orders (id int primary key, item text, qty int);",
"insert into orders values (1, 'widget', 10);",
"select dolt_add('.');",
"select dolt_commit('-m', 'initial order');",
"insert into orders values (2, 'gadget', 5);",
"select dolt_add('.');",
"select dolt_commit('-m', 'second order');",
fmt.Sprintf("select dolt_backup('add', 'named_bak', '%s');", backupUrl),
"select dolt_backup('sync', 'named_bak');",
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_named');", backupUrl),
"USE restored_named",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, item, qty from orders order by id;",
Expected: []sql.Row{{1, "widget", 10}, {2, "gadget", 5}},
},
{
// 4 commits: Initialize + CREATE DATABASE + 2 user commits.
Query: "select count(*) from dolt.commits;",
Expected: []sql.Row{{int64(4)}},
},
{
Query: "select message from dolt.commits order by date;",
Expected: []sql.Row{{"Initialize data repository"}, {"CREATE DATABASE"}, {"initial order"}, {"second order"}},
},
},
})
urlA := localBackupUrl(t, "backup_a")
urlB := localBackupUrl(t, "backup_b")
runBackupTest(t, ScriptTest{
Name: "dolt.dolt_backups table reflects add and remove",
SetUpScript: []string{
fmt.Sprintf("select dolt_backup('add', 'backup_a', '%s');", urlA),
fmt.Sprintf("select dolt_backup('add', 'backup_b', '%s');", urlB),
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from dolt.dolt_backups order by name;",
Expected: []sql.Row{{"backup_a"}, {"backup_b"}},
},
{
Query: "select dolt_backup('remove', 'backup_a');",
Expected: []sql.Row{{"{0}"}},
},
{
Query: "select name from dolt.dolt_backups order by name;",
Expected: []sql.Row{{"backup_b"}},
},
{
Query: "select dolt_backup('remove', 'backup_b');",
Expected: []sql.Row{{"{0}"}},
},
{
Query: "select count(*) from dolt.dolt_backups;",
Expected: []sql.Row{{int64(0)}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "restore --force overwrites an existing database and data is correct",
SetUpScript: []string{
"create table t (id int primary key, v text);",
"insert into t values (1, 'original');",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
// First restore creates db_to_overwrite.
fmt.Sprintf("select dolt_backup('restore', '%s', 'db_to_overwrite');", backupUrl),
},
Assertions: []ScriptTestAssertion{
{
// Without --force a second restore to the same name must error.
Query: fmt.Sprintf("select dolt_backup('restore', '%s', 'db_to_overwrite');", backupUrl),
ExpectedErr: "database 'db_to_overwrite' already exists, use '--force' to overwrite",
},
{
Query: fmt.Sprintf("select dolt_backup('restore', '--force', '%s', 'db_to_overwrite');", backupUrl),
Expected: []sql.Row{{"{0}"}},
},
{
Query: "USE db_to_overwrite;",
},
{
Query: "select id, v from t order by id;",
Expected: []sql.Row{{1, "original"}},
},
{
// Initialize + CREATE DATABASE + seed.
Query: "select count(*) from dolt.commits;",
Expected: []sql.Row{{int64(3)}},
},
{
Query: "USE postgres;",
},
{
Query: "drop database db_to_overwrite;",
Expected: []sql.Row{},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "multi-branch backup and restore preserves all branches",
SetUpScript: []string{
"create table products (id int primary key, name text);",
"insert into products values (1, 'alpha');",
"select dolt_add('.');",
"select dolt_commit('-m', 'add alpha on main');",
"select dolt_branch('feature');",
"select dolt_checkout('feature');",
"insert into products values (2, 'beta');",
"select dolt_commit('-Am', 'add beta on feature');",
"select dolt_checkout('main');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_branches');", backupUrl),
"USE restored_branches",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, name from products order by id;",
Expected: []sql.Row{{1, "alpha"}},
},
{
Query: "select name from dolt.branches order by name;",
Expected: []sql.Row{{"feature"}, {"main"}},
},
{
Query: "select dolt_checkout('feature');",
SkipResultsCheck: true,
},
{
Query: "select id, name from products order by id;",
Expected: []sql.Row{{1, "alpha"}, {2, "beta"}},
},
{
// Feature branch must have its own commit on top of main.
Query: "select message from dolt.commits order by date desc limit 1;",
Expected: []sql.Row{{"add beta on feature"}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "working set state is preserved in backup",
SetUpScript: []string{
"create table logs (id int primary key, msg text);",
"insert into logs values (1, 'committed');",
"select dolt_add('.');",
"select dolt_commit('-m', 'first commit');",
// Row 2: staged but not committed.
"insert into logs values (2, 'staged only');",
"select dolt_add('.');",
// Row 3: unstaged.
"insert into logs values (3, 'unstaged');",
// sync-url flushes the working set before syncing, so all rows are captured.
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_workingset');", backupUrl),
"USE restored_workingset",
},
Assertions: []ScriptTestAssertion{
{
// All three rows are visible: staged and unstaged changes are flushed to the
// working set before backup and are restored intact.
Query: "select id, msg from logs order by id;",
Expected: []sql.Row{{1, "committed"}, {2, "staged only"}, {3, "unstaged"}},
},
{
// Only the first user commit; rows 2 and 3 are in the working set only.
Query: "select count(*) from dolt.commits;",
Expected: []sql.Row{{int64(3)}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "incremental sync captures new commits added after the first sync",
SetUpScript: []string{
fmt.Sprintf("select dolt_backup('add', 'incr_bak', '%s');", backupUrl),
"create table events (id int primary key, name text);",
"insert into events values (1, 'first');",
"select dolt_add('.');",
"select dolt_commit('-m', 'first event');",
"select dolt_backup('sync', 'incr_bak');",
"insert into events values (2, 'second');",
"select dolt_add('.');",
"select dolt_commit('-m', 'second event');",
// Second (incremental) sync must include the new commit.
"select dolt_backup('sync', 'incr_bak');",
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_incr');", backupUrl),
"select dolt_backup('remove', 'incr_bak');",
"USE restored_incr",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, name from events order by id;",
Expected: []sql.Row{{1, "first"}, {2, "second"}},
},
{
// Initialize + CREATE DATABASE + 2 user commits.
Query: "select count(*) from dolt.commits;",
Expected: []sql.Row{{int64(4)}},
},
},
})
runBackupTest(t, ScriptTest{
Name: "sync nonexistent named backup returns error",
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_backup('sync', 'does_not_exist');",
ExpectedErr: "backup 'does_not_exist' not found",
},
},
})
runBackupTest(t, ScriptTest{
Name: "remove nonexistent backup returns error",
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_backup('remove', 'no_such_backup');",
ExpectedErr: "backup 'no_such_backup' not found",
},
},
})
runBackupTest(t, ScriptTest{
Name: "restore from nonexistent URL returns error",
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_backup('restore', 'file:///nonexistent/doltgres/backup/path', 'new_db');",
ExpectedErr: "ERROR",
},
},
})
urlA = localBackupUrl(t, "backup_a")
urlB = localBackupUrl(t, "backup_b")
runBackupTest(t, ScriptTest{
Name: "adding a backup with a duplicate name returns error",
SetUpScript: []string{
fmt.Sprintf("select dolt_backup('add', 'mybackup', '%s');", urlA),
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("select dolt_backup('add', 'mybackup', '%s');", urlB),
ExpectedErr: "backup 'mybackup' already exists",
},
{
Query: "select dolt_backup('remove', 'mybackup');",
Expected: []sql.Row{{"{0}"}},
},
},
})
runBackupTest(t, ScriptTest{
Name: "unknown dolt_backup operation returns error",
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_backup('typo', 'name', 'url');",
ExpectedErr: "unrecognized dolt_backup parameter 'typo'",
},
},
})
runBackupTest(t, ScriptTest{
Name: "dolt_backup with no args returns usage error",
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_backup();",
ExpectedErr: "use 'dolt_backups' table to list backups",
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "empty database backup and restore",
SetUpScript: []string{
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_empty');", backupUrl),
"USE restored_empty",
},
Assertions: []ScriptTestAssertion{
{
Query: "select count(*) from information_schema.tables where table_schema = 'public';",
Expected: []sql.Row{{int64(0)}},
},
{
// At minimum, Initialize + CREATE DATABASE commits must be present.
Query: "select count(*) >= 2 from dolt.commits;",
Expected: []sql.Row{{"t"}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "standalone sequence state is preserved across backup and restore",
SetUpScript: []string{
"create sequence counter start 1 increment 5;",
"select nextval('counter');", // advances to 1
"select nextval('counter');", // advances to 6
"select nextval('counter');", // advances to 11
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_seq');", backupUrl),
"USE restored_seq",
},
Assertions: []ScriptTestAssertion{
{
Query: "select sequence_name from information_schema.sequences where sequence_schema = 'public';",
Expected: []sql.Row{{"counter"}},
},
{
// Must continue from where it left off (after 11, next is 16), not reset to 1.
Query: "select nextval('counter');",
Expected: []sql.Row{{int64(16)}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "serial column sequence state is preserved across backup and restore",
SetUpScript: []string{
"create table widgets (id serial primary key, name text);",
"insert into widgets (name) values ('a'), ('b'), ('c');",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed widgets');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_serial');", backupUrl),
"USE restored_serial",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, name from widgets order by id;",
Expected: []sql.Row{{1, "a"}, {2, "b"}, {3, "c"}},
},
{
// Auto-generated id must continue from 4, proving the sequence state was restored.
Query: "insert into widgets (name) values ('d') returning id;",
Expected: []sql.Row{{4}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "views are preserved across backup and restore",
SetUpScript: []string{
"create table employees (id int primary key, dept text, salary int);",
"insert into employees values (1, 'eng', 100000), (2, 'eng', 120000), (3, 'ops', 90000);",
"create view eng_employees as select id, salary from employees where dept = 'eng';",
"select dolt_add('.');",
"select dolt_commit('-m', 'add employees and view');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_views');", backupUrl),
"USE restored_views",
},
Assertions: []ScriptTestAssertion{
{
Query: "select table_name from information_schema.views where table_schema = 'public';",
Expected: []sql.Row{{"eng_employees"}},
},
{
Query: "select id, salary from eng_employees order by id;",
Expected: []sql.Row{{1, 100000}, {2, 120000}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "foreign key constraints are preserved and enforced after restore",
SetUpScript: []string{
"create table categories (id int primary key, name text);",
"create table items (id int primary key, cat_id int references categories(id), label text);",
"insert into categories values (1, 'fruit'), (2, 'veg');",
"insert into items values (1, 1, 'apple'), (2, 2, 'carrot');",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed fk data');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_fk');", backupUrl),
"USE restored_fk",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, label from items order by id;",
Expected: []sql.Row{{1, "apple"}, {2, "carrot"}},
},
{
Query: "insert into items values (3, 99, 'mystery');",
ExpectedErr: "Foreign key",
},
{
Query: "select count(*) from information_schema.referential_constraints where constraint_schema = 'public';",
Expected: []sql.Row{{int64(1)}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "secondary index is preserved and used after restore",
SetUpScript: []string{
"create table products (id int primary key, sku text not null, price int);",
"create index idx_sku on products (sku);",
"insert into products values (1, 'AAA', 10), (2, 'BBB', 20), (3, 'CCC', 30);",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed products with index');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_idx');", backupUrl),
"USE restored_idx",
},
Assertions: []ScriptTestAssertion{
{
Query: "select indexname from pg_indexes where tablename = 'products' and indexname = 'idx_sku';",
Expected: []sql.Row{{"idx_sku"}},
},
{
Query: "select id, sku, price from products order by id;",
Expected: []sql.Row{{1, "AAA", 10}, {2, "BBB", 20}, {3, "CCC", 30}},
},
{
// A lookup that the optimizer can satisfy via the index must return the correct row.
Query: "select id, price from products where sku = 'BBB';",
Expected: []sql.Row{{2, 20}},
},
{
// Uniqueness is NOT enforced by this non-unique index, but the index must still
// accept new inserts with a duplicate sku value.
Query: "insert into products values (4, 'AAA', 99);",
Expected: []sql.Row{},
},
{
Query: "select id from products where sku = 'AAA' order by id;",
Expected: []sql.Row{{1}, {4}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "custom enum type is preserved across backup and restore",
SetUpScript: []string{
"create type my_mood as enum ('happy', 'ok', 'sad');",
"create table user_states (id int primary key, name text, mood my_mood);",
"insert into user_states values (1, 'alice', 'happy'), (2, 'bob', 'sad');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_types');", backupUrl),
"USE restored_types",
},
Assertions: []ScriptTestAssertion{
{
// pg_type is implemented and returns user-defined types.
Query: "select typname from pg_type where typname = 'my_mood';",
Expected: []sql.Row{{"my_mood"}},
},
{
Query: "select 'ok'::my_mood;",
Expected: []sql.Row{{"ok"}},
},
{
Query: "select id, name, mood::text from user_states order by id;",
Expected: []sql.Row{{1, "alice", "happy"}, {2, "bob", "sad"}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "domain type is preserved across backup and restore",
SetUpScript: []string{
`create domain pos_int as integer check (value > 0);`,
`create table measurements (id int primary key, val pos_int);`,
`insert into measurements values (1, 42);`,
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_domain');", backupUrl),
"USE restored_domain",
},
Assertions: []ScriptTestAssertion{
{
// typtype = 'd' for domain types.
Query: "select typname, typtype from pg_type where typname = 'pos_int';",
Expected: []sql.Row{{"pos_int", "d"}},
},
{
Query: "select val from measurements where id = 1;",
Expected: []sql.Row{{42}},
},
{
// Domain constraint must still be enforced after restore.
Query: "insert into measurements values (2, -1);",
ExpectedErr: "pos_int_check",
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "user-defined composite type is preserved across backup and restore",
SetUpScript: []string{
`create type point_t as (x float8, y float8);`,
`create function distance(p point_t) returns float8 as $$
begin return sqrt((p).x * (p).x + (p).y * (p).y); end;
$$ language plpgsql;`,
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_composite');", backupUrl),
"USE restored_composite",
},
Assertions: []ScriptTestAssertion{
{
// typtype = 'c' for composite types.
Query: "select typname, typtype from pg_type where typname = 'point_t';",
Expected: []sql.Row{{"point_t", "c"}},
},
{
Query: "select (row(3.0, 4.0)::point_t).x;",
Expected: []sql.Row{{float64(3.0)}},
},
{
// A function accepting the composite type must still be callable after restore.
Query: "select distance(row(3.0, 4.0)::point_t);",
Expected: []sql.Row{{float64(5.0)}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "user-defined function is preserved across backup and restore",
SetUpScript: []string{
`create function double_it(n int) returns int as $$
begin return n * 2; end;
$$ language plpgsql;`,
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_funcs');", backupUrl),
"USE restored_funcs",
},
Assertions: []ScriptTestAssertion{
{
// pg_proc is an unimplemented stub; calling the function is the best available proof.
Query: "select double_it(21);",
Expected: []sql.Row{{42}},
},
{
Query: "select double_it(0);",
Expected: []sql.Row{{0}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "stored procedure is preserved across backup and restore",
SetUpScript: []string{
"create table job_log (id int primary key, status text);",
"insert into job_log values (1, 'pending');",
`create procedure mark_done(job_id int) as $$
begin update job_log set status = 'done' where id = job_id; end;
$$ language plpgsql;`,
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_procs');", backupUrl),
"USE restored_procs",
},
Assertions: []ScriptTestAssertion{
{
// pg_proc is an unimplemented stub; calling the procedure is the best available proof.
Query: "call mark_done(1);",
SkipResultsCheck: true,
},
{
Query: "select status from job_log where id = 1;",
Expected: []sql.Row{{"done"}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "trigger is preserved and fires after restore",
SetUpScript: []string{
"create table readings (id int primary key, val int);",
`create function clamp_val() returns trigger as $$
begin
if NEW.val > 100 then NEW.val := 100; end if;
return NEW;
end;
$$ language plpgsql;`,
"create trigger clamp_trigger before insert on readings for each row execute function clamp_val();",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_triggers');", backupUrl),
"USE restored_triggers",
},
Assertions: []ScriptTestAssertion{
{
// pg_trigger is an unimplemented stub; the clamp firing is the best available proof.
// Inserting val=200 must be clamped to 100 by the trigger.
Query: "insert into readings values (1, 200);",
SkipResultsCheck: true,
},
{
Query: "select val from readings where id = 1;",
Expected: []sql.Row{{100}},
},
},
})
// TODO: Extension loading in Windows CI environments don't work currently
if !(runtime.GOOS == "windows" && os.Getenv("CI") != "") {
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "extension is preserved across backup and restore",
SetUpScript: []string{
`create extension "uuid-ossp";`,
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_ext');", backupUrl),
"USE restored_ext",
},
Assertions: []ScriptTestAssertion{
{
// pg_extension is an unimplemented stub; calling the extension function is the best available proof.
// uuid_generate_v4() returns a 36-character UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
Query: "select length(uuid_generate_v4()::text) = 36;",
Expected: []sql.Row{{"t"}},
},
},
})
}
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "named schema contents are preserved across backup and restore",
SetUpScript: []string{
"create schema inventory;",
"create table inventory.products (id int primary key, name text);",
"insert into inventory.products values (1, 'widget'), (2, 'gadget');",
"select dolt_commit('-Am', 'add inventory schema');",
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_schema');", backupUrl),
"USE restored_schema",
},
Assertions: []ScriptTestAssertion{
{
Query: "select schema_name from information_schema.schemata where schema_name = 'inventory';",
Expected: []sql.Row{{"inventory"}},
},
{
Query: "select id, name from inventory.products order by id;",
Expected: []sql.Row{{1, "widget"}, {2, "gadget"}},
},
},
})
backupUrl = localBackupUrl(t, "backup")
runBackupTest(t, ScriptTest{
Name: "custom cast is preserved across backup and restore",
SetUpScript: []string{
// CREATE TABLE implicitly creates a composite row type of the same name.
`CREATE TABLE cast_src (v text);`,
`CREATE TABLE cast_dst (v text, tag text);`,
`CREATE FUNCTION cast_src_to_dst(src cast_src) RETURNS cast_dst AS $$
SELECT ROW((src).v, 'casted')::cast_dst
$$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_verify(dst cast_dst) RETURNS text AS $$
SELECT (dst).v || ':' || (dst).tag
$$ LANGUAGE SQL;`,
`CREATE CAST (cast_src AS cast_dst) WITH FUNCTION cast_src_to_dst(cast_src);`,
fmt.Sprintf("select dolt_backup('sync-url', '%s');", backupUrl),
fmt.Sprintf("select dolt_backup('restore', '%s', 'restored_casts');", backupUrl),
"USE restored_casts",
},
Assertions: []ScriptTestAssertion{
{
// pg_cast is implemented and returns user-defined casts.
// context 'e' = explicit, method 'f' = function.
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_src' AND dst.typname = 'cast_dst';`,
Expected: []sql.Row{{"e", "f"}},
},
{
Query: `SELECT cast_verify((ROW('hello')::cast_src)::cast_dst);`,
Expected: []sql.Row{{"hello:casted"}},
},
},
})
}
+3362
View File
File diff suppressed because it is too large Load Diff
+911
View File
@@ -0,0 +1,911 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/stretchr/testify/require"
)
// localRemoteUrl returns a file:// URL for a fresh temp directory suitable for use as a Dolt
// remote. Each call produces a unique directory, so two calls with the same |name| never collide.
// We avoid using [testing.T.TempDir] here, because on Windows, NBS chunk files can still be
// memory-mapped and locked for a brief window after a server's controller reports itself stopped,
// which interfers with the cleanup.
func localRemoteUrl(t *testing.T, name string) string {
t.Helper()
safeName := strings.ReplaceAll(t.Name(), "/", "_")
dir, err := os.MkdirTemp(os.TempDir(), safeName+"_"+name)
require.NoError(t, err)
return "file://" + filepath.ToSlash(dir)
}
// runRemoteTest runs |script| inside a dedicated, freshly-started on-disk Doltgres server and
// tears the server down when the subtest completes.
//
// A new server is started for every call so that each push/pull/clone test begins with a clean
// database state. The server runs against a temporary on-disk directory (via
// CreateServerLocalWithPort) because pushing to, pulling from, or cloning a file:// remote
// requires a real NBS chunk store -- in-memory storage cannot be synced to a file:// URL.
//
// dolt_push/dolt_pull/dolt_fetch/dolt_clone/dolt_remote are all invoked with SELECT rather than
// CALL: Doltgres only allows built-in Dolt procedures to be invoked as functions (see
// ErrDoltProcedureSelectOnly in server/functions/dolt_procedures.go).
func runRemoteTest(t *testing.T, script ScriptTest) {
t.Helper()
t.Run(script.Name, func(t *testing.T) {
t.Helper()
port, err := sql.GetEmptyPort()
require.NoError(t, err)
ctx, conn, controller := CreateServerLocalWithPort(t, "postgres", port)
defer func() {
conn.Close(ctx)
controller.Stop()
require.NoError(t, controller.WaitForStop())
}()
runScript(t, ctx, script, conn, true)
})
}
// TestDoltRemote exercises the file-system remote push/pull/fetch/clone workflow end to end.
func TestDoltRemote(t *testing.T) {
remoteUrl := localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "push and clone preserve table data and commit history",
SetUpScript: []string{
"create table items (id int primary key, label text not null);",
"insert into items values (1, 'apple'), (2, 'banana');",
"select dolt_commit('-Am', 'first commit: add apple and banana');",
"insert into items values (3, 'cherry');",
"select dolt_commit('-Am', 'second commit: add cherry');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_items');", remoteUrl),
"USE cloned_items",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, label from items order by id;",
Expected: []sql.Row{{1, "apple"}, {2, "banana"}, {3, "cherry"}},
},
{
// Initialize + CREATE DATABASE + 2 user commits.
Query: "select count(*) from dolt.commits;",
Expected: []sql.Row{{int64(4)}},
},
{
Query: "select message from dolt.commits order by date;",
Expected: []sql.Row{{"Initialize data repository"}, {"CREATE DATABASE"}, {"first commit: add apple and banana"}, {"second commit: add cherry"}},
},
{
// dolt_clone automatically configures "origin" to point back at the source URL.
Query: "select name from dolt.remotes;",
Expected: []sql.Row{{"origin"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "standalone sequence state is preserved across push and clone",
SetUpScript: []string{
"create sequence counter start 1 increment 5;",
"select nextval('counter');", // advances to 1
"select nextval('counter');", // advances to 6
"select nextval('counter');", // advances to 11
"select dolt_commit('-Am', 'seed counter');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_seq');", remoteUrl),
"drop sequence counter", // delete the sequence in the first database
"USE cloned_seq",
},
Assertions: []ScriptTestAssertion{
{
Query: "select sequence_name, sequence_catalog from information_schema.sequences where sequence_schema = 'public';",
Expected: []sql.Row{{"counter", "cloned_seq"}},
},
{
// Must continue from where it left off (after 11, next is 16), not reset to 1.
Query: "select nextval('counter');",
Expected: []sql.Row{{int64(16)}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "serial column's owned sequence state is preserved across push and clone",
SetUpScript: []string{
"create table widgets (id serial primary key, name text);",
"insert into widgets (name) values ('a'), ('b'), ('c');",
"select dolt_commit('-Am', 'seed widgets');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_widgets');", remoteUrl),
"drop table widgets",
"USE cloned_widgets",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, name from widgets order by id;",
Expected: []sql.Row{{1, "a"}, {2, "b"}, {3, "c"}},
},
{
// Auto-generated id must continue from 4, proving the owned sequence's state was pushed.
Query: "insert into widgets (name) values ('d') returning id;",
Expected: []sql.Row{{4}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "identity column's owned sequence state is preserved across push and clone",
SetUpScript: []string{
"create table gadgets (id bigint generated by default as identity primary key, name text);",
"insert into gadgets (name) values ('a'), ('b'), ('c');",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed gadgets');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_gadgets');", remoteUrl),
"drop table gadgets",
"USE cloned_gadgets",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, name from gadgets order by id;",
Expected: []sql.Row{{1, "a"}, {2, "b"}, {3, "c"}},
},
{
Query: "insert into gadgets (name) values ('d') returning id;",
Expected: []sql.Row{{4}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "sequence in a non-public schema is preserved across push and clone",
SetUpScript: []string{
"create schema myschema;",
"create sequence myschema.seq2 start 100 increment 10;",
"select nextval('myschema.seq2');", // advances to 100
"select dolt_add('.');",
"select dolt_commit('-m', 'seed myschema.seq2');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_schema');", remoteUrl),
"USE cloned_schema",
},
Assertions: []ScriptTestAssertion{
{
Query: "select sequence_schema, sequence_name from information_schema.sequences where sequence_name = 'seq2';",
Expected: []sql.Row{{"myschema", "seq2"}},
},
{
// Must continue from 100 (after one call), not reset to the start value.
Query: "select nextval('myschema.seq2');",
Expected: []sql.Row{{int64(110)}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "incremental push and pull keep sequence and table state in sync",
SetUpScript: []string{
"create sequence counter start 1 increment 5;",
"create table orders (id int primary key default nextval('counter'), item text);",
"select dolt_commit('-Am', 'initial schema');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'replica');", remoteUrl),
// Advance the sequence and add data on the source side only.
"USE postgres",
"insert into orders (item) values ('widget');", // consumes nextval -> 1
"select dolt_commit('-Am', 'first order');",
"select dolt_push('origin', 'main');",
// Pull the change into the replica.
"USE replica",
"select dolt_pull('origin');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, item from orders order by id;",
Expected: []sql.Row{{1, "widget"}},
},
{
Query: "select nextval('counter');",
Expected: []sql.Row{{int64(6)}},
},
},
})
runRemoteTest(t, ScriptTest{
Name: "dolt_remotes reflects add and remove",
SetUpScript: []string{
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", localRemoteUrl(t, "remote_a")),
fmt.Sprintf("select dolt_remote('add', 'other', '%s');", localRemoteUrl(t, "remote_b")),
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from dolt_remotes order by name;",
Expected: []sql.Row{{"origin"}, {"other"}},
},
{
Query: "select dolt_remote('remove', 'other');",
Expected: []sql.Row{{"{0}"}},
},
{
Query: "select name from dolt_remotes;",
Expected: []sql.Row{{"origin"}},
},
{
Query: "select dolt_remote('remove', 'other');",
ExpectedErr: "unknown remote",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "fetch updates remote-tracking branches without touching the working branch; pull merges",
SetUpScript: []string{
"create table events (id int primary key, name text);",
"insert into events values (1, 'first');",
"select dolt_commit('-Am', 'first event');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'subscriber');", remoteUrl),
"USE postgres",
"insert into events values (2, 'second');",
"select dolt_commit('-Am', 'second event');",
"select dolt_push('origin', 'main');",
"USE subscriber",
"select dolt_fetch('origin', 'main');",
},
Assertions: []ScriptTestAssertion{
{
// fetch alone must not update the working branch.
Query: "select id, name from events order by id;",
Expected: []sql.Row{{1, "first"}},
},
{
Query: "select name from dolt_remote_branches;",
Expected: []sql.Row{{"remotes/origin/main"}},
},
{
Query: "select dolt_pull('origin');",
Expected: []sql.Row{{`{1,0,"merge successful"}`}},
},
{
// pull must fast-forward the working branch to match the remote.
Query: "select id, name from events order by id;",
Expected: []sql.Row{{1, "first"}, {2, "second"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pull fails when the working set has uncommitted changes",
SetUpScript: []string{
"create table logs (id int primary key, msg text);",
"insert into logs values (1, 'a');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'dirty_clone');", remoteUrl),
"USE postgres",
"insert into logs values (2, 'b');",
"select dolt_commit('-Am', 'second');",
"select dolt_push('origin', 'main');",
"USE dirty_clone",
// Leave an uncommitted change on the clone before pulling.
"insert into logs values (3, 'uncommitted');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_pull('origin');",
ExpectedErr: "cannot merge with uncommitted changes",
},
},
})
runRemoteTest(t, ScriptTest{
Name: "push without a configured remote returns an error",
SetUpScript: []string{
"create table t (id int primary key);",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_push('origin', 'main');",
ExpectedErr: "remote 'origin' not found",
},
},
})
runRemoteTest(t, ScriptTest{
Name: "pull without a configured remote returns an error",
SetUpScript: []string{
"create table t (id int primary key);",
"select dolt_add('.');",
"select dolt_commit('-m', 'seed');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_pull('origin');",
ExpectedErr: "remote 'origin' not found",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "custom enum type is preserved across push and clone",
SetUpScript: []string{
"create type mood as enum ('sad', 'ok', 'happy');",
"create table moods (id int primary key, m mood);",
"insert into moods values (1, 'happy'), (2, 'sad');",
"select dolt_commit('-Am', 'seed moods');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_moods');", remoteUrl),
"drop table moods;",
"drop type mood;",
"USE cloned_moods",
},
Assertions: []ScriptTestAssertion{
{
Query: "select id, m::text from moods order by id;",
Expected: []sql.Row{{1, "happy"}, {2, "sad"}},
},
{
// The enum type definition itself (not just data using it) must have transferred.
Query: "insert into moods values (3, 'ok') returning m::text;",
Expected: []sql.Row{{"ok"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "user-defined function is preserved across push and clone",
SetUpScript: []string{
"create function double_it(x int) returns int as $$ begin return x * 2; end; $$ language plpgsql;",
"select dolt_commit('-Am', 'seed function');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_func');", remoteUrl),
"drop function double_it();",
"USE cloned_func",
},
Assertions: []ScriptTestAssertion{
{
Query: "select double_it(21);",
Expected: []sql.Row{{42}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pushing and cloning a non-default branch carries its own sequence and table state",
SetUpScript: []string{
"create sequence counter start 1 increment 1;",
"create table gadgets (id int primary key default nextval('counter'), name text);",
"insert into gadgets (name) values ('a'), ('b'), ('c');", // consumes 1, 2, 3
"select dolt_commit('-Am', 'seed on main');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
"select dolt_checkout('-b', 'feature');",
"insert into gadgets (name) values ('feature-only');", // consumes 4
"select dolt_commit('-Am', 'feature work');",
"select dolt_push('origin', 'feature');",
fmt.Sprintf("select dolt_clone('--branch', 'feature', '%s', 'cloned_feature');", remoteUrl),
"drop table gadgets;",
"drop sequence counter;",
"USE cloned_feature",
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from gadgets order by id;",
Expected: []sql.Row{{"a"}, {"b"}, {"c"}, {"feature-only"}},
},
{
// The owned sequence must reflect the feature branch's state (after 4), not main's.
Query: "insert into gadgets (name) values ('next') returning id;",
Expected: []sql.Row{{5}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "non-fast-forward push is rejected, then succeeds with --force",
SetUpScript: []string{
"create table t (id int primary key, note text);",
"insert into t values (1, 'source');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'clone1');", remoteUrl),
// Diverge history on both sides so neither is a fast-forward of the other.
"insert into t values (2, 'source-diverged');",
"select dolt_commit('-Am', 'source diverges');",
"select dolt_push('origin', 'main');",
"USE clone1",
"insert into t values (3, 'clone-diverged');",
"select dolt_commit('-Am', 'clone diverges');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_push('origin', 'main');",
ExpectedErr: "failed to push some refs",
},
{
Query: "select dolt_push('-f', 'origin', 'main');",
SkipResultsCheck: true,
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pushing a branch delete removes it from the remote",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
"select dolt_checkout('-b', 'doomed');",
"insert into t values (2);",
"select dolt_commit('-Am', 'doomed work');",
"select dolt_push('origin', 'doomed');",
fmt.Sprintf("select dolt_clone('%s', 'observer');", remoteUrl),
"USE observer",
"select dolt_fetch('origin', 'doomed');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from dolt_remote_branches order by name;",
Expected: []sql.Row{{"remotes/origin/doomed"}, {"remotes/origin/main"}},
},
{
Query: "USE postgres",
SkipResultsCheck: true,
},
{
// A ":branch" refspec deletes the branch on the remote, just like the CLI's `dolt push origin :doomed`.
Query: "select dolt_push('origin', ':doomed');",
SkipResultsCheck: true,
},
{
Query: "USE observer",
SkipResultsCheck: true,
},
{
Query: "select dolt_fetch('--prune', 'origin');",
SkipResultsCheck: true,
},
{
Query: "select name from dolt_remote_branches order by name;",
Expected: []sql.Row{{"remotes/origin/main"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "tags are pushed and fetched along with commits",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
"select dolt_tag('v1.0', '-m', 'first release');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
"select dolt_push('origin', 'v1.0');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_tags');", remoteUrl),
"USE cloned_tags",
},
Assertions: []ScriptTestAssertion{
{
Query: "select tag_name from dolt_tags;",
Expected: []sql.Row{{"v1.0"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "fetch supports an explicit refspec into a custom tracking ref",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'observer');", remoteUrl),
"USE observer",
"select dolt_fetch('origin', 'refs/heads/main:refs/remotes/custom/main');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select name from dolt_remote_branches where name = 'remotes/custom/main';",
Expected: []sql.Row{{"remotes/custom/main"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "push --all pushes every local branch",
SetUpScript: []string{
"create table t (id int primary key);",
"insert into t values (1);",
"select dolt_commit('-Am', 'seed');",
"select dolt_checkout('-b', 'branch_a');",
"select dolt_checkout('main');",
"select dolt_checkout('-b', 'branch_b');",
"select dolt_checkout('main');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('--all', 'origin');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_all');", remoteUrl),
"USE cloned_all",
},
Assertions: []ScriptTestAssertion{
{
// Clone only checks out a local branch for the default branch; the others land as
// remote-tracking refs, proving --all pushed them all to the remote.
Query: "select name from dolt_remote_branches order by name;",
Expected: []sql.Row{{"remotes/origin/branch_a"}, {"remotes/origin/branch_b"}, {"remotes/origin/main"}},
},
},
})
runRemoteTest(t, ScriptTest{
Name: "adding a remote with a name that already exists returns an error",
SetUpScript: []string{
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", localRemoteUrl(t, "remote_a")),
},
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", localRemoteUrl(t, "remote_b")),
ExpectedErr: "remote already exists",
},
},
})
runRemoteTest(t, ScriptTest{
Name: "adding a remote with an invalid name returns an error",
Assertions: []ScriptTestAssertion{
{
Query: fmt.Sprintf("select dolt_remote('add', 'bad name', '%s');", localRemoteUrl(t, "remote")),
ExpectedErr: "remote name invalid",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "fetching an invalid ref spec returns an error",
SetUpScript: []string{
"create table t (id int primary key);",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_fetch('origin', 'garbage');",
ExpectedErr: "invalid ref spec",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pull auto-merges non-conflicting divergent history",
SetUpScript: []string{
"create table t (id int primary key, v text);",
"insert into t values (1, 'a'), (2, 'b');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'clone1');", remoteUrl),
"USE postgres",
"insert into t values (3, 'source-row');",
"select dolt_commit('-Am', 'source adds row 3');",
"select dolt_push('origin', 'main');",
"USE clone1",
"insert into t values (4, 'clone-row');",
"select dolt_commit('-Am', 'clone adds row 4');",
},
Assertions: []ScriptTestAssertion{
{
// Divergent but non-conflicting: no fast-forward, no conflicts, and it merges automatically.
Query: "select dolt_pull('origin');",
Expected: []sql.Row{{`{0,0,"merge successful"}`}},
},
{
Query: "select id, v from t order by id;",
Expected: []sql.Row{{1, "a"}, {2, "b"}, {3, "source-row"}, {4, "clone-row"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "pull surfaces real merge conflicts, resolvable via dolt_conflicts_resolve",
SetUpScript: []string{
"create table t (id int primary key, v text);",
"insert into t values (1, 'a'), (2, 'b');",
"select dolt_commit('-Am', 'seed');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'clone1');", remoteUrl),
"USE postgres",
"update t set v = 'source-edit' where id = 1;",
"select dolt_commit('-Am', 'source edits row 1');",
"select dolt_push('origin', 'main');",
"USE clone1",
"update t set v = 'clone-edit' where id = 1;",
"select dolt_commit('-Am', 'clone edits row 1');",
// Conflicts abort the implicit transaction under autocommit unless explicitly allowed.
"set dolt_allow_commit_conflicts to 1;",
},
Assertions: []ScriptTestAssertion{
{
Query: "select dolt_pull('origin');",
Expected: []sql.Row{{`{0,1,"merge has unresolved conflicts or constraint violations"}`}},
},
{
Query: "select base_v, our_v, their_v from dolt_conflicts_t;",
Expected: []sql.Row{{"a", "clone-edit", "source-edit"}},
},
{
Query: "select dolt_conflicts_resolve('--ours', 't');",
SkipResultsCheck: true,
},
{
Query: "select count(*) from dolt_conflicts;",
Expected: []sql.Row{{int64(0)}},
},
{
// --ours keeps the clone's own edit.
Query: "select id, v from t order by id;",
Expected: []sql.Row{{1, "clone-edit"}, {2, "b"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "domain type and its check constraint are preserved across push and clone",
SetUpScript: []string{
"create domain pos_int as integer check (value > 0);",
"create table measurements (id int primary key, val pos_int);",
"insert into measurements values (1, 42);",
"select dolt_commit('-Am', 'seed measurements');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_domain');", remoteUrl),
"drop table measurements;",
"drop domain pos_int;",
"USE cloned_domain",
},
Assertions: []ScriptTestAssertion{
{
// typtype = 'd' for domain types.
Query: "select typname, typtype from pg_type where typname = 'pos_int';",
Expected: []sql.Row{{"pos_int", "d"}},
},
{
Query: "select val from measurements where id = 1;",
Expected: []sql.Row{{42}},
},
{
// The domain's check constraint must still be enforced after cloning.
Query: "insert into measurements values (2, -1);",
ExpectedErr: "pos_int_check",
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "user-defined composite type is preserved across push and clone",
SetUpScript: []string{
`create type point_t as (x float8, y float8);`,
`create function distance(p point_t) returns float8 as $$ begin return sqrt((p).x * (p).x + (p).y * (p).y); end; $$ language plpgsql;`,
"select dolt_commit('-Am', 'seed composite type');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_composite');", remoteUrl),
"drop function distance(point_t);",
"drop type point_t;",
"USE cloned_composite",
},
Assertions: []ScriptTestAssertion{
{
// typtype = 'c' for composite types.
Query: "select typname, typtype from pg_type where typname = 'point_t';",
Expected: []sql.Row{{"point_t", "c"}},
},
{
Query: "select distance(row(3.0, 4.0)::point_t);",
Expected: []sql.Row{{float64(5.0)}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "stored procedure is preserved across push and clone",
SetUpScript: []string{
"create table job_log (id int primary key, status text);",
"insert into job_log values (1, 'pending');",
`create procedure mark_done(job_id int) as $$ begin update job_log set status = 'done' where id = job_id; end; $$ language plpgsql;`,
"select dolt_commit('-Am', 'seed procedure');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_proc');", remoteUrl),
"drop procedure mark_done(int);",
"drop table job_log;",
"USE cloned_proc",
},
Assertions: []ScriptTestAssertion{
{
Query: "call mark_done(1);",
SkipResultsCheck: true,
},
{
Query: "select status from job_log where id = 1;",
Expected: []sql.Row{{"done"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "trigger is preserved and fires after clone",
SetUpScript: []string{
"create table readings (id int primary key, val int);",
`create function clamp_val() returns trigger as $$ begin if NEW.val > 100 then NEW.val := 100; end if; return NEW; end; $$ language plpgsql;`,
"create trigger clamp_trigger before insert on readings for each row execute function clamp_val();",
"select dolt_commit('-Am', 'seed trigger');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_trigger');", remoteUrl),
"drop trigger clamp_trigger on readings;",
"drop function clamp_val();",
"drop table readings;",
"USE cloned_trigger",
},
Assertions: []ScriptTestAssertion{
{
// Inserting val=200 must be clamped to 100 by the trigger, proving it still fires post-clone.
Query: "insert into readings values (1, 200);",
SkipResultsCheck: true,
},
{
Query: "select val from readings where id = 1;",
Expected: []sql.Row{{100}},
},
},
})
// TODO: Extension loading in Windows CI environments don't work currently
if !(runtime.GOOS == "windows" && os.Getenv("CI") != "") {
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "extension is preserved across push and clone",
SetUpScript: []string{
`create extension "uuid-ossp";`,
"select dolt_commit('-Am', 'seed extension');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_ext');", remoteUrl),
"USE cloned_ext",
},
Assertions: []ScriptTestAssertion{
{
// pg_extension is an unimplemented stub; calling the extension function is the best available proof.
// uuid_generate_v4() returns a 36-character UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
Query: "select length(uuid_generate_v4()::text) = 36;",
Expected: []sql.Row{{"t"}},
},
},
})
}
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "named schema contents are preserved across push and clone",
SetUpScript: []string{
"create schema inventory;",
"create table inventory.products (id int primary key, name text);",
"insert into inventory.products values (1, 'widget'), (2, 'gadget');",
"select dolt_commit('-Am', 'add inventory schema');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_schema_contents');", remoteUrl),
"drop table inventory.products;",
"drop schema inventory;",
"USE cloned_schema_contents",
},
Assertions: []ScriptTestAssertion{
{
Query: "select schema_name from information_schema.schemata where schema_name = 'inventory';",
Expected: []sql.Row{{"inventory"}},
},
{
Query: "select id, name from inventory.products order by id;",
Expected: []sql.Row{{1, "widget"}, {2, "gadget"}},
},
},
})
remoteUrl = localRemoteUrl(t, "remote")
runRemoteTest(t, ScriptTest{
Name: "custom cast is preserved across push and clone",
SetUpScript: []string{
// CREATE TABLE implicitly creates a composite row type of the same name.
`CREATE TABLE cast_src (v text);`,
`CREATE TABLE cast_dst (v text, tag text);`,
`CREATE FUNCTION cast_src_to_dst(src cast_src) RETURNS cast_dst AS $$ SELECT ROW((src).v, 'casted')::cast_dst $$ LANGUAGE SQL;`,
`CREATE FUNCTION cast_verify(dst cast_dst) RETURNS text AS $$ SELECT (dst).v || ':' || (dst).tag $$ LANGUAGE SQL;`,
`CREATE CAST (cast_src AS cast_dst) WITH FUNCTION cast_src_to_dst(cast_src);`,
"select dolt_commit('-Am', 'seed cast');",
fmt.Sprintf("select dolt_remote('add', 'origin', '%s');", remoteUrl),
"select dolt_push('origin', 'main');",
fmt.Sprintf("select dolt_clone('%s', 'cloned_cast');", remoteUrl),
"drop cast (cast_src as cast_dst);",
"drop function cast_src_to_dst(cast_src);",
"drop function cast_verify(cast_dst);",
"drop table cast_src;",
"drop table cast_dst;",
"USE cloned_cast",
},
Assertions: []ScriptTestAssertion{
{
// context 'e' = explicit, method 'f' = function.
Query: `SELECT c.castcontext::text, c.castmethod::text
FROM pg_cast c
JOIN pg_type src ON src.oid = c.castsource
JOIN pg_type dst ON dst.oid = c.casttarget
WHERE src.typname = 'cast_src' AND dst.typname = 'cast_dst';`,
Expected: []sql.Row{{"e", "f"}},
},
{
Query: `SELECT cast_verify((ROW('hello')::cast_src)::cast_dst);`,
Expected: []sql.Row{{"hello:casted"}},
},
},
})
}
+2907
View File
File diff suppressed because one or more lines are too long
+347
View File
@@ -0,0 +1,347 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestDomain(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "create domain",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE DOMAIN year AS integer CONSTRAINT not_null_c NOT NULL CONSTRAINT null_c NULL;`,
ExpectedErr: `conflicting NULL/NOT NULL constraints`,
},
{
Query: `CREATE DOMAIN year AS integer NULL NOT NULL;`,
ExpectedErr: `conflicting NULL/NOT NULL constraints`,
},
{
Query: `CREATE DOMAIN year AS integer DEFAULT 1999 NOT NULL CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));`,
Expected: []sql.Row{},
},
{
Query: `CREATE DOMAIN year AS integer CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));`,
ExpectedErr: `type "year" already exists`,
},
{
Query: `CREATE DOMAIN year_with_check AS integer CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));`,
Expected: []sql.Row{},
},
{
Query: `CREATE DOMAIN year_with_two_checks AS integer CONSTRAINT year_check_min CHECK (VALUE >= 1901) CONSTRAINT year_check_max CHECK (VALUE <= 2155);`,
Expected: []sql.Row{},
},
{
Query: `CREATE TABLE test_table (id int primary key, v non_existing_domain);`,
ExpectedErr: `type "non_existing_domain" does not exist`,
},
{
Query: `SELECT conname, contype, conrelid, contypid from pg_constraint WHERE conname IN ('year_check', 'year_check_min', 'year_check_max') ORDER BY conname;`,
Expected: []sql.Row{
{"year_check", "c", 0, 2637102637},
{"year_check", "c", 0, 1287570634},
{"year_check_max", "c", 0, 2938087575},
{"year_check_min", "c", 0, 2938087575},
},
},
},
},
{
Name: "multiple checks",
SetUpScript: []string{},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE DOMAIN d1 AS integer CONSTRAINT check1 CHECK (VALUE > 100) CONSTRAINT check2 CHECK (VALUE < 200);`,
},
{
Query: `SELECT conname, contype, conrelid, contypid from pg_constraint WHERE conname IN ('check1', 'check2') ORDER BY conname;`,
Expected: []sql.Row{
{"check1", "c", 0, 2005192443},
{"check2", "c", 0, 2005192443},
},
},
{
Query: "create table t1 (pk int primary key, v d1);",
},
{
Query: "insert into t1 values (1, 50);",
ExpectedErr: "constraint \"check1\"",
},
{
Query: "insert into t1 values (2, 150);",
},
{
Query: "insert into t1 values (3, 250);",
ExpectedErr: "constraint \"check2\"",
},
{
Query: "CREATE DOMAIN d2 AS integer CHECK (VALUE > 300) CHECK (VALUE < 400);",
},
{
Query: `SELECT conname, contype, conrelid, contypid from pg_constraint WHERE conname IN ('d2_check', 'd2_check1') ORDER BY conname;`,
Expected: []sql.Row{
{"d2_check", "c", 0, 1691630863},
{"d2_check1", "c", 0, 1691630863},
},
},
{
Query: "CREATE DOMAIN d3 AS integer CONSTRAINT d3_check1 CHECK (VALUE > 300) CHECK (VALUE < 400);",
},
{
// TODO: this is slightly different behavior from Postgres, but the important thing is two different names are generated
Query: `SELECT conname, contype, conrelid, contypid from pg_constraint WHERE conname IN ('d3_check1', 'd3_check') ORDER BY conname;`,
Expected: []sql.Row{
{"d3_check", "c", 0, 2529148428},
{"d3_check1", "c", 0, 2529148428},
},
},
},
},
{
Name: "create table with domain type",
SetUpScript: []string{
`CREATE DOMAIN year AS integer CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));`,
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE TABLE table_with_domain (pk int primary key, y year);`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO table_with_domain VALUES (1, 1999)`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO table_with_domain VALUES (2, 1899)`,
ExpectedErr: `constraint "year_check"`,
},
{
Query: `SELECT * FROM table_with_domain`,
Expected: []sql.Row{{1, 1999}},
},
},
},
{
Name: "create table with domain type with default value",
SetUpScript: []string{
`CREATE DOMAIN year AS integer DEFAULT 2000;`,
`CREATE TABLE table_with_domain_with_default (pk int primary key, y year);`,
`INSERT INTO table_with_domain_with_default VALUES (1, 1999)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `INSERT INTO table_with_domain_with_default(pk) VALUES (2)`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM table_with_domain_with_default`,
Expected: []sql.Row{{1, 1999}, {2, 2000}},
},
},
},
{
Name: "create table with domain type with not null constraint",
SetUpScript: []string{
`CREATE DOMAIN year AS integer NOT NULL;`,
`CREATE TABLE tbl_not_null (pk int primary key, y year);`,
`INSERT INTO tbl_not_null VALUES (1, 1999)`,
},
Assertions: []ScriptTestAssertion{
{
// TODO: the correct error msg: `domain year does not allow null values`
Query: `INSERT INTO tbl_not_null VALUES (2, null)`,
ExpectedErr: `column name 'y' is non-nullable but attempted to set a value of null`,
},
{
// TODO: the correct error msg: `domain year does not allow null values`
Query: `INSERT INTO tbl_not_null(pk) VALUES (2)`,
ExpectedErr: `Field 'y' doesn't have a default value`,
},
{
Query: `SELECT * FROM tbl_not_null`,
Expected: []sql.Row{{1, 1999}},
},
},
},
{
Name: "update on table with domain type",
SetUpScript: []string{
`CREATE DOMAIN year AS integer NOT NULL CONSTRAINT year_check_min CHECK (VALUE >= 1901) CONSTRAINT year_check_max CHECK (VALUE <= 2155);`,
`CREATE TABLE test_table (pk int primary key, y year);`,
`INSERT INTO test_table VALUES (1, 1999), (2, 2000)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `UPDATE test_table SET y = 1902 WHERE pk = 1;`,
Expected: []sql.Row{},
},
{
Query: `UPDATE test_table SET y = 1900 WHERE pk = 1;`,
ExpectedErr: `constraint "year_check_min"`,
},
{
// TODO: the correct error msg: `domain year does not allow null values`
Query: `UPDATE test_table SET y = null WHERE pk = 1;`,
ExpectedErr: `column name 'y' is non-nullable but attempted to set a value of null`,
},
{
Query: `SELECT * FROM test_table`,
Expected: []sql.Row{{1, 1902}, {2, 2000}},
},
},
},
{
Name: "domain type as text type",
SetUpScript: []string{
`CREATE DOMAIN non_empty_string AS text NULL CONSTRAINT name_check CHECK (VALUE <> '');`,
`CREATE TABLE non_empty_string_t (id int primary key, first_name non_empty_string, last_name non_empty_string);`,
`INSERT INTO non_empty_string_t VALUES (1, 'John', 'Doe')`,
},
Assertions: []ScriptTestAssertion{
{
Query: `INSERT INTO non_empty_string_t VALUES (2, 'Jane', 'Doe')`,
Expected: []sql.Row{},
},
{
Query: `UPDATE non_empty_string_t SET last_name = '' WHERE first_name = 'Jane'`,
ExpectedErr: `Check constraint "name_check" violated`,
},
{
Query: `UPDATE non_empty_string_t SET last_name = NULL WHERE first_name = 'Jane'`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM non_empty_string_t`,
Expected: []sql.Row{{1, "John", "Doe"}, {2, "Jane", nil}},
},
},
},
{
Name: "drop domain",
SetUpScript: []string{
`CREATE DOMAIN year AS integer CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));`,
`CREATE TABLE table_with_domain (pk int primary key, y year);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `DROP DOMAIN year;`,
ExpectedErr: `cannot drop type year because other objects depend on it`,
},
{
Query: `DROP TABLE table_with_domain;`,
Expected: []sql.Row{},
},
{
Query: `DROP DOMAIN year;`,
Expected: []sql.Row{},
},
{
Query: `DROP DOMAIN IF EXISTS year;`,
Expected: []sql.Row{},
},
{
Query: `DROP DOMAIN IF EXISTS postgres.public.year;`,
Expected: []sql.Row{},
},
{
Query: `DROP DOMAIN IF EXISTS mydb.public.year;`,
ExpectedErr: `DROP DOMAIN is currently only supported for the current database`,
},
{
Query: `DROP DOMAIN non_existing_domain;`,
ExpectedErr: `type "non_existing_domain" does not exist`,
},
},
},
{
Name: "domain array type column",
SetUpScript: []string{
`CREATE DOMAIN vc4 AS varchar(4);`,
`CREATE TABLE t (pk int primary key, v vc4[]);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `INSERT INTO t VALUES (1, array['ab', 'cd']::vc4[]);`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO t VALUES (2, '{ef,gh}');`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM t ORDER BY pk;`,
Expected: []sql.Row{{1, "{ab,cd}"}, {2, "{ef,gh}"}},
},
},
},
{
Name: "explicit cast to domain type",
SetUpScript: []string{
`CREATE DOMAIN year_not_null AS integer NOT NULL CONSTRAINT year_check CHECK (((VALUE >= 1901) AND (VALUE <= 2155)));`,
`CREATE TABLE test_table (year integer);`,
`INSERT INTO test_table VALUES (2000), (2024);`,
`CREATE TABLE my_table (id integer);`,
`INSERT INTO my_table VALUES (2000), (2002);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 1903::year_not_null;`,
Expected: []sql.Row{{1903}},
},
{
Query: `SELECT 1903::year_not_null::text;`,
Expected: []sql.Row{{"1903"}},
},
{
Query: `SELECT 1900::year_not_null;`,
ExpectedErr: `value for domain year_not_null violates check constraint "year_check"`,
},
{
Query: `SELECT NULL::year_not_null;`,
ExpectedErr: `domain year_not_null does not allow null values`,
},
{
Query: `SELECT year::year_not_null from test_table order by year;`,
Expected: []sql.Row{{2000}, {2024}},
},
{
Query: `INSERT INTO test_table VALUES (null);`,
},
{
Query: `SELECT year::year_not_null from test_table;`,
ExpectedErr: `domain year_not_null does not allow null values`,
},
{
Query: `SELECT id::year_not_null from my_table order by id;`,
Expected: []sql.Row{{2000}, {2002}},
},
{
Query: `INSERT INTO my_table VALUES (2156);`,
},
{
Query: `SELECT id::year_not_null from my_table;`,
ExpectedErr: `value for domain year_not_null violates check constraint "year_check"`,
},
},
},
})
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import "testing"
func TestDropDatabase(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "simple create database",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE testdb",
},
{
Query: "DROP DATABASE testdb",
},
},
},
{
Name: "with quotes",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE testdb",
},
{
Query: "DROP DATABASE \"testdb\"",
},
},
},
{
Name: "with hyphen",
Assertions: []ScriptTestAssertion{
{
Query: "CREATE DATABASE \"test-db\"",
},
{
Query: "USE \"test-db\"",
},
},
},
{
Name: "if exists",
Assertions: []ScriptTestAssertion{
{
Query: "DROP DATABASE IF EXISTS invalid",
},
},
},
})
}
+299
View File
@@ -0,0 +1,299 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestDropFunction(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Function does not exist",
Assertions: []ScriptTestAssertion{
{
Query: "DROP FUNCTION doesnotexist;",
ExpectedErr: "does not exist",
},
{
Query: "DROP FUNCTION IF EXISTS doesnotexist;",
Expected: []sql.Row{},
},
},
},
{
Name: "Basic cases",
SetUpScript: []string{`
CREATE FUNCTION func1() RETURNS TEXT AS $$
BEGIN RETURN 'func1'; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func2(input INT) RETURNS TEXT AS $$
BEGIN RETURN 'func2(INT)'; END;
$$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT func1(), func2(99);",
Expected: []sql.Row{{"func1", "func2(INT)"}},
},
{
Query: "DROP FUNCTION func1;",
Expected: []sql.Row{},
},
{
Query: "DROP FUNCTION func2(INT);",
Expected: []sql.Row{},
},
{
Query: "SELECT func1();",
ExpectedErr: "not found",
},
{
Query: "SELECT func2(99);",
ExpectedErr: "not found",
},
},
},
{
Name: "Optional type information",
SetUpScript: []string{`
CREATE FUNCTION func1() RETURNS TEXT AS $$
BEGIN RETURN 'func1'; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func2() RETURNS TEXT AS $$
BEGIN RETURN 'func2'; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func3(input INT) RETURNS TEXT AS $$
BEGIN RETURN 'func3(INT)'; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func4(input INT) RETURNS TEXT AS $$
BEGIN RETURN 'func4(INT)'; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func5(input INT, foo TEXT) RETURNS TEXT AS $$
BEGIN RETURN 'func5(INT, TEXT)'; END;
$$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT func1(), func2(), func3(1), func4(2);",
Expected: []sql.Row{{"func1", "func2", "func3(INT)", "func4(INT)"}},
},
{
Query: "DROP FUNCTION func1(OUT TEXT);",
Expected: []sql.Row{},
},
{
Query: "DROP FUNCTION func2(OUT paramname TEXT);",
Expected: []sql.Row{},
},
{
Query: "DROP FUNCTION func3(paramname INT);",
Expected: []sql.Row{},
},
{
Query: "DROP FUNCTION func4(IN paramname INT);",
Expected: []sql.Row{},
},
{
Query: "DROP FUNCTION func5(IN paramname INT, IN paramname TEXT);",
Expected: []sql.Row{},
},
},
},
{
Name: "Qualified names",
SetUpScript: []string{`
CREATE FUNCTION func1() RETURNS TEXT AS $$
BEGIN RETURN 'func1'; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func2(input TEXT) RETURNS TEXT AS $$
BEGIN RETURN 'func2(TEXT)'; END;
$$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT current_schema(), current_database();",
Expected: []sql.Row{{"public", "postgres"}},
},
{
Query: "SELECT func1(), func2('foo');",
Expected: []sql.Row{{"func1", "func2(TEXT)"}},
},
{
Query: "DROP FUNCTION public.func1;",
Expected: []sql.Row{},
},
{
Query: "SELECT func1();",
ExpectedErr: "not found",
},
{
Query: "DROP FUNCTION postgres.public.func2(TEXT);",
Expected: []sql.Row{},
},
{
Query: "SELECT func2('w00t');",
ExpectedErr: "not found",
},
},
},
{
// When there is only one function with a name, the parameter types are not required,
// but if the name is not unique, an error is returned.
Name: "Unspecified parameter types",
SetUpScript: []string{`
CREATE FUNCTION func1(input1 TEXT, input2 TEXT) RETURNS int AS $$
BEGIN RETURN 42; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func2(input1 TEXT) RETURNS int AS $$
BEGIN RETURN 42; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func2(input1 TEXT, input2 TEXT) RETURNS int AS $$
BEGIN RETURN 42; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func3(input1 TEXT, input2 TEXT) RETURNS int AS $$
BEGIN RETURN 42; END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func3() RETURNS int AS $$
BEGIN RETURN 42; END;
$$ LANGUAGE plpgsql;`},
Assertions: []ScriptTestAssertion{
{
Query: "DROP FUNCTION func1;",
Expected: []sql.Row{},
},
{
Query: "DROP FUNCTION func2;",
ExpectedErr: "is not unique",
},
{
Query: "DROP FUNCTION func3;",
Expected: []sql.Row{},
},
},
},
{
// TODO: Postgres supports specifying multiple functions to drop, but our
// parser doesn't seem to support parsing multiple functions yet.
Skip: true,
Name: "Multiple functions",
SetUpScript: []string{`
CREATE FUNCTION func1() RETURNS TEXT AS $$
BEGIN
RETURN 'func1';
END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func2(input TEXT) RETURNS TEXT AS $$
BEGIN
RETURN 'func2(TEXT)';
END;
$$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT func1(), func2('foo');",
Expected: []sql.Row{{"func1", "func2(TEXT)"}},
},
{
Query: "DELETE FUNCTION func1, func2(TExT);",
Expected: []sql.Row{},
},
},
},
{
Name: "Overloaded functions",
SetUpScript: []string{`
CREATE FUNCTION func2(input TEXT) RETURNS TEXT AS $$
BEGIN
RETURN 'func2(TEXT)';
END;
$$ LANGUAGE plpgsql;`, `
CREATE FUNCTION func2(input INT) RETURNS TEXT AS $$
BEGIN
RETURN 'func2(INT)';
END;
$$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT func2('foo'), func2(42);",
Expected: []sql.Row{{"func2(TEXT)", "func2(INT)"}},
},
{
Query: "DROP FUNCTION func2(TEXT);",
Expected: []sql.Row{},
},
{
Query: "SELECT func2('foo'::text);",
ExpectedErr: "does not exist",
},
{
Query: "SELECT func2(42);",
Expected: []sql.Row{{"func2(INT)"}},
},
{
Query: "DROP FUNCTION func2(INT);",
Expected: []sql.Row{},
},
{
Query: "SELECT func2(42);",
ExpectedErr: "not found",
},
},
},
{
Name: "drop function with empty search_path",
SetUpScript: []string{
`SELECT pg_catalog.set_config('search_path', '', false);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `DROP FUNCTION IF EXISTS public.vmstate(s integer);`,
Expected: []sql.Row{},
},
},
},
{
Name: "user defined type used as parameter",
SetUpScript: []string{
`CREATE TABLE public.trans (vmid integer NOT NULL);`,
`CREATE FUNCTION public.tax_job_trans(t public.trans) RETURNS public.trans
LANGUAGE plpgsql
AS '
BEGIN
SELECT * FROM public.trans;
END;
';`,
},
Assertions: []ScriptTestAssertion{
{
Query: `DROP FUNCTION IF EXISTS public.tax_job_trans(t public.trans);`,
Expected: []sql.Row{},
},
},
},
{
Name: "drop non existing function with non existing type",
Assertions: []ScriptTestAssertion{
{
Query: "DROP FUNCTION IF EXISTS public.tax_job_trans(t public.trans);",
Expected: []sql.Row{},
},
},
},
})
}
+242
View File
@@ -0,0 +1,242 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestDropProcedure(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Procedure does not exist",
Assertions: []ScriptTestAssertion{
{
Query: "DROP PROCEDURE doesnotexist;",
ExpectedErr: "does not exist",
},
{
Query: "DROP PROCEDURE IF EXISTS doesnotexist;",
Expected: []sql.Row{},
},
},
},
{
Name: "Basic cases",
SetUpScript: []string{
`CREATE PROCEDURE proc1() AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc2(input INT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CALL proc1();",
Expected: []sql.Row{},
},
{
Query: "CALL proc2(99);",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc1;",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc2(INT);",
Expected: []sql.Row{},
},
{
Query: "CALL proc1();",
ExpectedErr: "does not exist",
},
{
Query: "CALL proc2(99);",
ExpectedErr: "does not exist",
},
},
},
{
Name: "Optional type information",
SetUpScript: []string{
`CREATE PROCEDURE proc1() AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc2() AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc3(input INT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc4(input INT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc5(input INT, foo TEXT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CALL proc1();",
Expected: []sql.Row{},
},
{
Query: "CALL proc2();",
Expected: []sql.Row{},
},
{
Query: "CALL proc3(1);",
Expected: []sql.Row{},
},
{
Query: "CALL proc4(2);",
Expected: []sql.Row{},
},
{
Query: "CALL proc5(3, 'abc');",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc1(OUT TEXT);",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc2(OUT paramname TEXT);",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc3(paramname INT);",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc4(IN paramname INT);",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc5(IN paramname INT, IN paramname TEXT);",
Expected: []sql.Row{},
},
{
Query: "CALL proc1();",
ExpectedErr: "does not exist",
},
{
Query: "CALL proc2();",
ExpectedErr: "does not exist",
},
{
Query: "CALL proc3(1);",
ExpectedErr: "does not exist",
},
{
Query: "CALL proc4(2);",
ExpectedErr: "does not exist",
},
{
Query: "CALL proc5(3, 'abc');",
ExpectedErr: "does not exist",
},
},
},
{
Name: "Qualified names",
SetUpScript: []string{
`CREATE PROCEDURE proc1() AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc2(input TEXT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT current_schema(), current_database();",
Expected: []sql.Row{{"public", "postgres"}},
},
{
Query: "CALL proc1();",
Expected: []sql.Row{},
},
{
Query: "CALL proc2('foo');",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE public.proc1;",
Expected: []sql.Row{},
},
{
Query: "CALL proc1();",
ExpectedErr: "does not exist",
},
{
Query: "DROP PROCEDURE postgres.public.proc2(TEXT);",
Expected: []sql.Row{},
},
{
Query: "CALL proc2('bar');",
ExpectedErr: "does not exist",
},
},
},
{
Name: "Unspecified parameter types",
SetUpScript: []string{
`CREATE PROCEDURE proc1(input1 TEXT, input2 TEXT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc2(input1 TEXT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc2(input1 TEXT, input2 TEXT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc3(input1 TEXT, input2 TEXT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc3() AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "DROP PROCEDURE proc1;",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc2;",
ExpectedErr: "is not unique",
},
{
Query: "DROP PROCEDURE proc3;",
Expected: []sql.Row{},
},
},
},
{
Name: "Overloaded procedures",
SetUpScript: []string{
`CREATE PROCEDURE proc2(input TEXT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
`CREATE PROCEDURE proc2(input INT) AS $$ BEGIN NULL; END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: "CALL proc2('foo');",
Expected: []sql.Row{},
},
{
Query: "CALL proc2(42);",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc2(TEXT);",
Expected: []sql.Row{},
},
{
Query: "CALL proc2('foo'::text);",
ExpectedErr: "does not exist",
},
{
Query: "CALL proc2(42);",
Expected: []sql.Row{},
},
{
Query: "DROP PROCEDURE proc2(INT);",
Expected: []sql.Row{},
},
{
Query: "CALL proc2(42);",
ExpectedErr: "does not exist",
},
},
},
})
}
+109
View File
@@ -0,0 +1,109 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestDropTable(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "DROP TABLE on table type on column",
SetUpScript: []string{
`CREATE TABLE test1 (pk INT4 PRIMARY KEY, v1 TEXT);`,
`CREATE TABLE test2 (v1 test1);`,
`INSERT INTO test2 VALUES (ROW(1, 'abc')::test1);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `DROP TABLE test1;`,
ExpectedErr: "cannot drop table test1 because other objects depend on it\ncolumn v1 of table test2 depends on type test1",
},
{
Query: `DROP TABLE test2;`,
Expected: []sql.Row{},
},
{
Query: `DROP TABLE test1;`,
Expected: []sql.Row{},
},
},
},
{
Name: "DROP TABLE on table type on function parameter",
SetUpScript: []string{
`CREATE TABLE test (pk INT4 PRIMARY KEY, v1 TEXT);`,
`CREATE FUNCTION example_func(t test) RETURNS INT4 AS $$ BEGIN RETURN t.pk * 2; END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: `DROP TABLE test;`,
ExpectedErr: "cannot drop table test because other objects depend on it\nfunction example_func(test) depends on type test",
},
{
Query: `DROP FUNCTION example_func(test);`,
Expected: []sql.Row{},
},
{
Query: `DROP TABLE test;`,
Expected: []sql.Row{},
},
},
},
{
Name: "DROP TABLE on table type on procedure parameter",
SetUpScript: []string{
`CREATE TABLE test1 (pk INT4 PRIMARY KEY, v1 TEXT);`,
`CREATE TABLE test2 (v1 INT4);`,
`CREATE PROCEDURE example_proc(input test1) AS $$ BEGIN INSERT INTO test2 VALUES (input.pk); END; $$ LANGUAGE plpgsql;`,
},
Assertions: []ScriptTestAssertion{
{
Query: `DROP TABLE test1;`,
ExpectedErr: "cannot drop table test1 because other objects depend on it\nfunction example_proc(test1) depends on type test1",
},
{
Query: `DROP PROCEDURE example_proc(test1);`,
Expected: []sql.Row{},
},
{
Query: `DROP TABLE test1;`,
Expected: []sql.Row{},
},
},
},
{
Name: "DROP TABLE on table type on column concurrent",
SetUpScript: []string{
`CREATE TABLE test1 (pk INT4 PRIMARY KEY, v1 TEXT);`,
`CREATE TABLE test2 (v1 test1);`,
`INSERT INTO test2 VALUES (ROW(1, 'abc')::test1);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `DROP TABLE test1;`,
ExpectedErr: "cannot drop table test1 because other objects depend on it\ncolumn v1 of table test2 depends on type test1",
},
{
Query: `DROP TABLE test1, test2;`,
Expected: []sql.Row{},
},
},
},
})
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestExplain(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "basic explain tests",
SetUpScript: []string{
`CREATE TABLE t (i INT PRIMARY KEY)`,
},
Assertions: []ScriptTestAssertion{
{
Skip: true, // Our explain output is very different
Query: `EXPLAIN SELECT * FROM T;`,
Expected: []sql.Row{
{"Seq Scan on t (cost=0.00..35.50 rows=2550 width=4)"},
},
},
{
Skip: true, // Need to properly support explain options
Query: `
EXPLAIN
(
ANALYZE,
VERBOSE,
COSTS,
SETTINGS,
BUFFERS,
WAL,
TIMING,
SUMMARY,
FORMAT TEXT
)
SELECT * FROM t;
`,
Expected: []sql.Row{
{"Seq Scan on t (cost=0.00..35.50 rows=2550 width=4)"},
},
},
{
Skip: true, // Need to properly support explain options
Query: `
EXPLAIN
(
ANALYZE ON,
VERBOSE OFF,
COSTS TRUE,
SETTINGS FALSE,
BUFFERS,
WAL,
TIMING,
SUMMARY,
FORMAT TEXT
)
SELECT * FROM t;
`,
Expected: []sql.Row{
{"Seq Scan on t (cost=0.00..35.50 rows=2550 width=4)"},
},
},
{
Skip: true, // Need to properly support explain options
Query: `
EXPLAIN
(
NOTAVALIDOPTION
)
SELECT * FROM t;
`,
ExpectedErr: "ERROR: unrecognized EXPLAIN option \"NOTAVALIDOPTION\"",
},
},
},
})
}
+566
View File
@@ -0,0 +1,566 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestIn(t *testing.T) {
RunScriptsWithoutNormalization(t, []ScriptTest{
anyTests("ANY"),
anyTests("SOME"),
{
Name: "IN",
SetUpScript: []string{
`CREATE TABLE test (id INT);`,
`INSERT INTO test VALUES (1), (3), (2);`,
`CREATE TABLE test2 (id INT, test_id INT, txt text);`,
`INSERT INTO test2 VALUES (1, 1, 'foo'), (2, 10, 'bar'), (3, 2, 'baz');`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM test WHERE id IN (2, 3, 4, 5);`,
Expected: []sql.Row{{int32(3)}, {int32(2)}},
},
{
Query: `SELECT * FROM test WHERE id IN (4, 3, 2, 1, 0);`,
Expected: []sql.Row{{int32(1)}, {int32(3)}, {int32(2)}},
},
{
Query: `SELECT * FROM test2 WHERE test_id IN (SELECT * FROM test WHERE id = 2);`,
Expected: []sql.Row{{int32(3), int32(2), "baz"}},
},
{
Query: `SELECT * FROM test2 WHERE test_id IN(SELECT * FROM test WHERE id > 0);`,
Expected: []sql.Row{
{int32(1), int32(1), "foo"},
{int32(3), int32(2), "baz"},
},
},
{
Query: `SELECT 4 IN (null, 1, 2, 3);`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT 4 IN (null, 1, 2, 3, 4);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT NULL IN (null, 1, 2, 3);`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT 4 IN (1, 2, 3, null::int4);`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT 4 IN (1, 2, 3);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 4 IN (1, 2, 3, 4);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT concat('a', 'b') in ('a', 'b', 'ab');`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT concat('a', 'b') in ('a', 'b');`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT concat('a', 'b') in ('a', NULL, 'b');`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT concat('a', NULL) in ('a', 'b', 'ab');`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT concat('a', NULL) in ('a', NULL);`,
Expected: []sql.Row{{"t"}},
},
},
},
{
Name: "NOT IN",
SetUpScript: []string{
`CREATE TABLE test (id INT);`,
`INSERT INTO test VALUES (1), (3), (2);`,
`CREATE TABLE test2 (id INT, test_id INT, txt text);`,
`INSERT INTO test2 VALUES (1, 1, 'foo'), (2, 10, 'bar'), (3, 2, 'baz');`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM test WHERE id NOT IN (2, 3, 4, 5);`,
Expected: []sql.Row{{int32(1)}},
},
{
Query: `SELECT * FROM test2 WHERE test_id NOT IN (SELECT * FROM test WHERE id = 2);`,
Expected: []sql.Row{{int32(1), int32(1), "foo"}, {int32(2), int32(10), "bar"}},
},
{
Query: `SELECT * FROM test2 WHERE test_id NOT IN (SELECT * FROM test WHERE id > 0);`,
Expected: []sql.Row{
{int32(2), int32(10), "bar"},
},
},
{
Query: `SELECT 4 NOT IN (null, 1, 2, 3);`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT NULL NOT IN (null, 1, 2, 3);`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT 4 NOT IN (1, 2, 3);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 4 NOT IN (1, 2, 3, 4);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT concat('a', 'b') NOT in ('a', 'b');`,
Expected: []sql.Row{{"t"}},
},
},
},
})
}
func anyTests(name string) ScriptTest {
tests := []ScriptTestAssertion{
{
Query: `SELECT 3 = %s (ARRAY[1, 2, 3, 4, 5]);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 3 = %s (ARRAY[1, 2, 4, 5]);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 'a' = %s (ARRAY['c', 'a', 't']);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 'a' = %s (ARRAY['c', 'at', 't']);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 3 = %s (ARRAY[1.0, 2.1, 3.0, 5]);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 6 > %s (ARRAY[1, 2, 3, 4, 5]);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 6 < %s (ARRAY[1, 2, 3, 4, 5]);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 6 <= %s (ARRAY[1, 2, 3, 4, 5]);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 6 >= %s (ARRAY[1, 2, 3, 6, 5]);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT * FROM test WHERE id = %s(ARRAY[2, 3, 4, 5]);`,
Expected: []sql.Row{{int32(3)}, {int32(2)}},
},
{
Query: `SELECT * FROM test WHERE id = %s(ARRAY[4, 3, 2, 1, 0]);`,
Expected: []sql.Row{{int32(1)}, {int32(3)}, {int32(2)}},
},
{
Query: `SELECT * FROM test WHERE id = %s(ARRAY[4, 5, 6]);`,
Expected: []sql.Row{},
},
{
Query: `SELECT id FROM test3 WHERE 4 = %s(carr);`,
Expected: []sql.Row{
{int32(2)},
},
},
{
Query: `SELECT * FROM test2 WHERE test_id = %s(SELECT * FROM test WHERE id = 2);`,
Expected: []sql.Row{{int32(3), int32(2), "baz"}},
},
{
Query: `SELECT * FROM test2 WHERE test_id = %s(SELECT * FROM test WHERE id = 10);`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM test2 WHERE test_id = %s(SELECT * FROM test WHERE id > 1) AND txt = 'baz';`,
Expected: []sql.Row{{int32(3), int32(2), "baz"}},
},
{
Query: `SELECT * FROM test2 WHERE test_id > %s(SELECT * FROM test);`,
Expected: []sql.Row{
{int32(2), int32(10), "bar"},
{int32(3), int32(2), "baz"},
},
},
{
Query: `SELECT * FROM test2 WHERE test_id = %s(SELECT * FROM test WHERE id > 0);`,
Expected: []sql.Row{
{int32(1), int32(1), "foo"},
{int32(3), int32(2), "baz"},
},
},
{
Query: `SELECT "ns"."nspname" AS "table_schema",
"t"."relname" AS "table_name",
"cnst"."conname" AS "constraint_name",
pg_get_constraintdef("cnst"."oid") AS "expression",
CASE "cnst"."contype"
WHEN 'p' THEN 'PRIMARY'
WHEN 'u' THEN 'UNIQUE'
WHEN 'c' THEN 'CHECK'
WHEN 'x' THEN 'EXCLUDE'
END AS "constraint_type",
"a"."attname" AS "column_name"
FROM "pg_catalog"."pg_constraint" "cnst"
INNER JOIN "pg_catalog"."pg_class" "t" ON "t"."oid" = "cnst"."conrelid"
INNER JOIN "pg_catalog"."pg_namespace" "ns" ON "ns"."oid" = "cnst"."connamespace"
LEFT JOIN "pg_catalog"."pg_attribute" "a" ON "a"."attrelid" = "cnst"."conrelid" AND "a"."attnum" = %s ("cnst"."conkey")
WHERE "t"."relkind" IN ('r', 'p') AND (("ns"."nspname" = 'public' AND "t"."relname" = 'test2'));`,
Expected: []sql.Row{
{"public", "test2", "test2_pkey", "PRIMARY KEY (id)", "PRIMARY", "id"},
},
},
}
formattedTests := make([]ScriptTestAssertion, len(tests))
for i, test := range tests {
formattedTests[i] = ScriptTestAssertion{
Query: fmt.Sprintf(test.Query, name),
Skip: test.Skip,
Expected: test.Expected,
ExpectedErr: test.ExpectedErr,
}
}
return ScriptTest{
Name: name,
SetUpScript: []string{
`CREATE TABLE test (id INT);`,
`INSERT INTO test VALUES (1), (3), (2);`,
`CREATE TABLE test2 (id INT PRIMARY KEY, test_id INT, txt text);`,
`INSERT INTO test2 VALUES (1, 1, 'foo'), (2, 10, 'bar'), (3, 2, 'baz');`,
`CREATE TABLE test3 (id INT PRIMARY KEY, carr smallint[]);`,
`INSERT INTO test3 VALUES (1, ARRAY[1, 2, 3]), (2, ARRAY[4, 5, 6]);`,
},
Assertions: formattedTests,
}
}
func TestBinaryLogic(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "AND",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 1 = 1 AND 2 = 2;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT (1 = 1 AND 2 = 2) AND (false);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT (1 > 1 AND 2 = 2);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT (1 = 1 AND 2 = 2) AND (false);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT (1 = 1 AND 2 = 2) AND (true);`,
Expected: []sql.Row{{"t"}},
},
},
},
{
Name: "OR",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 1 = 1 OR 2 = 2;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT (1 = 1 AND 2 = 2) OR (false);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT (1 > 1 OR 2 = 2);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT (1 > 1 OR 2 > 2);`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT (1 > 1 OR 2 > 2) OR (true);`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT (1 = 1 AND 2 = 2) OR (true);`,
Expected: []sql.Row{{"t"}},
},
},
},
{
Name: "IS DISTINCT FROM",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 1 IS DISTINCT FROM 2;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 2 IS DISTINCT FROM 2;`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT null IS DISTINCT FROM 2;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT null IS DISTINCT FROM null;`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 2 IS DISTINCT FROM null;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 2 IS DISTINCT FROM 2.5;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 2 IS DISTINCT FROM '2';`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 2 IS DISTINCT FROM 'a';`,
ExpectedErr: `invalid input syntax for type int4: "a"`,
},
},
},
{
Name: "IS NOT DISTINCT FROM",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT 1 IS NOT DISTINCT FROM 2;`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 2 IS NOT DISTINCT FROM 2;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT null IS NOT DISTINCT FROM 2;`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT null IS NOT DISTINCT FROM null;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 2 IS NOT DISTINCT FROM null;`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 2 IS NOT DISTINCT FROM 2.5;`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT 2 IS NOT DISTINCT FROM '2';`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT 2 IS NOT DISTINCT FROM 'a';`,
ExpectedErr: `invalid input syntax for type int4: "a"`,
},
},
},
})
}
// Note that our parser is more forgiving of array subscripts than the actual Postgres parser.
// We can handle this: SELECT ARRAY[1, 2, 3][1]
// But postgres requires: SELECT (ARRAY[1, 2, 3])[1]
func TestSubscript(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "array literal",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT ARRAY[1, 2, 3][1];`,
Expected: []sql.Row{{1}},
},
{
Query: `SELECT (ARRAY[1, 2, 3])[3];`,
Expected: []sql.Row{{3}},
},
{
Query: `SELECT (ARRAY[1, 2, 3])[1+1];`,
Expected: []sql.Row{{2}},
},
{
Query: `SELECT ARRAY[1, 2, 3][0];`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT ARRAY[1, 2, 3][4];`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT ARRAY[1, 2, 3][null];`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT ARRAY['a', 'b', 'c'][2];`,
Expected: []sql.Row{{"b"}},
},
{
Query: `SELECT ARRAY[1, 2, 3][1:3];`,
ExpectedErr: "not yet supported",
},
{
Query: `SELECT ARRAY[1, 2, 3]['abc'];`,
ExpectedErr: "integer: unhandled type: string",
},
},
},
{
Name: "array column",
SetUpScript: []string{
`CREATE TABLE test (id INT, arr INT[]);`,
`INSERT INTO test VALUES (1, ARRAY[1, 2, 3]), (2, ARRAY[4, 5, 6]);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT arr[2] FROM test order by 1;`,
Expected: []sql.Row{{2}, {5}},
},
},
},
{
Name: "array subquery",
SetUpScript: []string{
"CREATE TABLE test (id INT);",
"INSERT INTO test VALUES (1), (2), (3);",
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT (array(select id from test order by 1))[2]`,
Expected: []sql.Row{{2}},
},
},
},
})
}
func TestCoalesce(t *testing.T) {
RunScripts(t, []ScriptTest{
{
// https://github.com/dolthub/doltgresql/issues/2332
Name: "COALESCE(NULL, col) in UPDATE",
SetUpScript: []string{
`CREATE TABLE t (id UUID PRIMARY KEY, val INTEGER NOT NULL DEFAULT 0, d DATE)`,
`INSERT INTO t VALUES ('00000000-0000-0000-0000-000000000001', 42, '2026-01-01')`,
},
Assertions: []ScriptTestAssertion{
{
// Should be a no-op; val stays 42.
Query: `UPDATE t SET val = COALESCE(NULL, val) WHERE id = '00000000-0000-0000-0000-000000000001'`,
SkipResultsCheck: true,
},
{
Query: `SELECT val FROM t WHERE id = '00000000-0000-0000-0000-000000000001'`,
Expected: []sql.Row{{int32(42)}},
},
{
// Should be a no-op; d stays '2026-01-01'.
Query: `UPDATE t SET d = COALESCE(NULL, d) WHERE id = '00000000-0000-0000-0000-000000000001'`,
SkipResultsCheck: true,
},
{
Query: `SELECT d FROM t WHERE id = '00000000-0000-0000-0000-000000000001'`,
Expected: []sql.Row{{"2026-01-01"}},
},
},
},
{
Name: "COALESCE type resolution in SELECT",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT COALESCE(NULL, 42)`,
Expected: []sql.Row{{int32(42)}},
},
{
Query: `SELECT COALESCE(NULL, NULL)`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT COALESCE(NULL, NULL, 'hello')`,
Expected: []sql.Row{{"hello"}},
},
{
Query: `SELECT COALESCE(1, 2, 3)`,
Expected: []sql.Row{{int32(1)}},
},
{
Query: `SELECT COALESCE(NULL, 2, 3)`,
Expected: []sql.Row{{int32(2)}},
},
{
// Explicit cast workaround still works.
Query: `SELECT COALESCE(NULL::integer, 42)`,
Expected: []sql.Row{{int32(42)}},
},
},
},
{
Name: "COALESCE with mixed types",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT COALESCE('a'::TEXT, 1::INTEGER)`,
ExpectedErr: `types text and integer cannot be matched`,
},
{
// smallint and bigint are compatible via numeric promotion; result is bigint.
Query: `SELECT COALESCE(NULL, 1::SMALLINT, 2::BIGINT) AS v;`,
Expected: []sql.Row{{int64(1)}},
},
},
},
})
}
+2048
View File
File diff suppressed because it is too large Load Diff
+950
View File
@@ -0,0 +1,950 @@
// Copyright 2023 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"bufio"
"context"
"encoding/json"
goerrors "errors"
"fmt"
"math"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/cockroachdb/apd/v3"
"github.com/cockroachdb/errors"
"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
"github.com/dolthub/dolt/go/libraries/doltcore/env"
"github.com/dolthub/dolt/go/libraries/utils/filesys"
"github.com/dolthub/dolt/go/libraries/utils/svcs"
"github.com/dolthub/go-mysql-server/sql"
gmstypes "github.com/dolthub/go-mysql-server/sql/types"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgtype"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dolthub/doltgresql/core/id"
"github.com/dolthub/doltgresql/postgres/parser/duration"
pgtree "github.com/dolthub/doltgresql/postgres/parser/sem/tree"
"github.com/dolthub/doltgresql/postgres/parser/timeofday"
"github.com/dolthub/doltgresql/postgres/parser/uuid"
dserver "github.com/dolthub/doltgresql/server"
"github.com/dolthub/doltgresql/server/auth"
"github.com/dolthub/doltgresql/server/functions"
"github.com/dolthub/doltgresql/server/types"
"github.com/dolthub/doltgresql/servercfg"
"github.com/dolthub/doltgresql/servercfg/cfgdetails"
)
// runOnPostgres is a debug setting to redirect the test framework to a local running postgres server,
// rather than starting a doltgres server.
const runOnPostgres = false
// serverHost is the host of the local Doltgres server used for testing; set to IPv4 loopback address.
var serverHost = "127.0.0.1"
// ScriptTest defines a consistent structure for testing queries.
type ScriptTest struct {
// Name of the script.
Name string
// The database to create and use. If not provided, then it defaults to "postgres".
Database string
// The SQL statements to execute as setup, in order. Results are not checked, but statements must not error.
SetUpScript []string
// The set of assertions to make after setup, in order
Assertions []ScriptTestAssertion
// When using RunScripts, setting this on one (or more) tests causes RunScripts to ignore all tests that have this
// set to false (which is the default value). This allows a developer to easily "focus" on a specific test without
// having to comment out other tests, pull it into a different function, etc. In addition, CI ensures that this is
// false before passing, meaning this prevents the commented-out situation where the developer forgets to uncomment
// their code.
Focus bool
// Skip is used to completely skip a test including setup
Skip bool
// UseLocalFileSystem determines if the test should use the local filesystem
UseLocalFileSystem bool
}
// ExpectedNotice specifies what notices are expected during a script test assertion.
type ExpectedNotice struct {
Severity string
Message string
}
// ScriptTestAssertion are the assertions upon which the script executes its main "testing" logic.
type ScriptTestAssertion struct {
Query string
Expected []sql.Row // Expected or ExpectedRaw should be used, but not both at the same time
ExpectedRaw [][][]byte // ExpectedRaw or Expected should be used, but not both at the same time
ExpectedErr string
ExpectedNotices []ExpectedNotice
Focus bool
BindVars []any
// SkipResultsCheck is used to skip assertions on the expected rows returned from a query. For now, this is
// included as some messages do not have a full logical implementation. Skipping the results check allows us to
// force the test client to not send of those messages.
SkipResultsCheck bool
// Skip is used to completely skip a test, not execute its query at all, and record it as a skipped test
// in the test suite results.
Skip bool
// Username specifies the user's name to use for the command. This creates a new connection, using the given name.
// By default (when the string is empty), the `postgres` superuser account is used. Any consecutive queries that
// have the same username and password will reuse the same connection. The `postgres` superuser account will always
// reuse the same connection. Do note that specifying the `postgres` account manually will create a connection
// that is different from the primary one.
Username string
// Password specifies the password that will be used alongside the given username. This field is essentially ignored
// when no username is given. If a username is given and the password is empty, then it is assumed that the password
// is the empty string.
Password string
// ExpectedTag is used to check the command tag returned from the server.
// This is checked only if no Expected is defined
ExpectedTag string
// ExpectedColNames are used to check the column names returned from the server.
ExpectedColNames []string
// ExpectedColTypes are used to check the column types returned from the server.
ExpectedColTypes []id.Type
// CopyFromSTDIN is used to test the COPY FROM STDIN command.
CopyFromStdInFile string
}
// EmptyCommandTag is special command tag placeholder to check for the empty string
const EmptyCommandTag = "EMPTY_COMMAND_TAG"
// Connection contains the default and current connections.
type Connection struct {
Default *pgx.Conn
Current *pgx.Conn
Username string
Password string
}
// receivedNotices tracks the NOTICE messages received over the connection to the Doltgres server, so that tests
// can assert what notices are expected to be sent to the client.
var receivedNotices []*pgconn.Notice
// RunScript runs the given script.
func RunScript(t *testing.T, script ScriptTest, normalizeRows bool) {
scriptDatabase := script.Database
if len(scriptDatabase) == 0 {
scriptDatabase = "postgres"
}
var ctx context.Context
var conn *Connection
if runOnPostgres {
ctx = context.Background()
pgxConn, err := pgx.Connect(ctx, fmt.Sprintf("postgres://postgres:password@127.0.0.1:%d/%s?sslmode=disable", 5432, scriptDatabase))
require.NoError(t, err)
conn = &Connection{
Default: pgxConn,
Current: pgxConn,
}
require.NoError(t, pgxConn.Ping(ctx))
defer func() {
conn.Close(ctx)
}()
} else {
var controller *svcs.Controller
if script.UseLocalFileSystem {
port, err := sql.GetEmptyPort()
require.NoError(t, err)
ctx, conn, controller = CreateServerLocalWithPort(t, scriptDatabase, port)
} else {
ctx, conn, controller = CreateServer(t, scriptDatabase)
}
defer func() {
conn.Close(ctx)
controller.Stop()
err := controller.WaitForStop()
require.NoError(t, err)
}()
}
t.Run(script.Name, func(t *testing.T) {
runScript(t, ctx, script, conn, normalizeRows)
})
}
// runScript runs the script given on the postgres connection provided
func runScript(t *testing.T, ctx context.Context, script ScriptTest, conn *Connection, normalizeRows bool) {
if script.Skip {
t.Skip("Skip has been set in the script")
}
// Run the setup
for _, query := range script.SetUpScript {
_, err := conn.Exec(ctx, query)
require.NoError(t, err, "error running setup query: %s", query)
}
assertions := script.Assertions
// First, we'll run through the scripts to check for the Focus variable. If it's true, then append it to the new slice.
focusAssertions := make([]ScriptTestAssertion, 0, len(script.Assertions))
for _, assertion := range script.Assertions {
if assertion.Focus {
// If this is running in GitHub Actions, then we'll panic, because someone forgot to disable it before committing
if _, ok := os.LookupEnv("GITHUB_ACTION"); ok {
panic(fmt.Sprintf("The assertion `%s` has Focus set to `true`. GitHub Actions requires that "+
"all tests are run, which Focus circumvents, leading to this error. Please disable Focus on "+
"all tests.", assertion.Query))
}
focusAssertions = append(focusAssertions, assertion)
}
}
// If we have assertions with Focus set, then we replace the normal script slice with the new slice.
if len(focusAssertions) > 0 {
assertions = focusAssertions
}
// Run the assertions
for _, assertion := range assertions {
t.Run(assertion.Query, func(t *testing.T) {
if assertion.Skip {
t.Skip("Skip has been set in the assertion")
}
// Clear out any previously received notices
receivedNotices = nil
// Use the provided username and password to create a new connection (if a username has been specified).
// This will automatically handle connection reuse, using the default connection is no user is specified, etc.
if err := conn.Connect(ctx, assertion.Username, assertion.Password); err != nil {
if assertion.ExpectedErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), assertion.ExpectedErr)
} else {
require.NoError(t, err)
}
return
}
// If we're skipping the results check, then we call Execute, as it uses a simplified message model.
if assertion.CopyFromStdInFile != "" {
copyFromStdin(t, conn.Current, assertion.Query, assertion.CopyFromStdInFile)
} else if assertion.SkipResultsCheck || assertion.ExpectedErr != "" {
_, err := conn.Exec(ctx, assertion.Query, assertion.BindVars...)
if assertion.ExpectedErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), assertion.ExpectedErr)
} else {
require.NoError(t, err)
}
} else if assertion.ExpectedTag != "" {
commandTag, err := conn.Exec(ctx, assertion.Query)
require.NoError(t, err)
tag := assertion.ExpectedTag
if tag == EmptyCommandTag {
tag = ""
}
assert.Equal(t, tag, commandTag.String())
} else {
rows, err := conn.Query(ctx, assertion.Query, assertion.BindVars...)
require.NoError(t, err)
readRows, readRawRows, err := ReadRows(rows, normalizeRows)
require.NoError(t, err)
if assertion.ExpectedColNames != nil {
fields := rows.FieldDescriptions()
if assert.Len(t, fields, len(assertion.ExpectedColNames), "expected length of columns") {
for i, col := range assertion.ExpectedColNames {
assert.Equal(t, col, fields[i].Name)
}
}
}
if assertion.ExpectedColTypes != nil {
fields := rows.FieldDescriptions()
if assert.Len(t, fields, len(assertion.ExpectedColTypes),
"columns returned and types expected are not the same length") {
for i, colId := range assertion.ExpectedColTypes {
assert.Equal(t, id.Cache().ToOID(colId.AsId()), fields[i].DataTypeOID,
`"%s" expected type "%s" but received "%s"`, fields[i].Name,
colId.TypeName(), id.Type(id.Cache().ToInternal(fields[i].DataTypeOID)).TypeName())
}
}
}
// not an exact match but works well enough for our tests
orderBy := strings.Contains(strings.ToLower(assertion.Query), "order by")
if assertion.ExpectedRaw != nil {
if orderBy {
assert.Equal(t, assertion.ExpectedRaw, readRawRows, "wrong result for query %s", assertion.Query)
} else {
assert.ElementsMatch(t, assertion.ExpectedRaw, readRawRows, "wrong result for query %s", assertion.Query)
}
} else {
if normalizeRows {
if orderBy {
assert.Equal(t, NormalizeExpectedRow(rows.FieldDescriptions(), assertion.Expected), readRows, "wrong result for query %s", assertion.Query)
} else {
assert.ElementsMatch(t, NormalizeExpectedRow(rows.FieldDescriptions(), assertion.Expected), readRows, "wrong result for query %s", assertion.Query)
}
} else {
if orderBy {
assert.Equal(t, assertion.Expected, readRows, "wrong result for query %s", assertion.Query)
} else {
assert.ElementsMatch(t, assertion.Expected, readRows, "wrong result for query %s", assertion.Query)
}
}
}
if len(assertion.ExpectedNotices) > 0 {
if len(assertion.ExpectedNotices) == len(receivedNotices) {
for i, notice := range receivedNotices {
assert.Equal(t, assertion.ExpectedNotices[i].Severity, notice.Severity)
assert.Equal(t, assertion.ExpectedNotices[i].Message, notice.Message)
}
} else {
if len(receivedNotices) == 0 {
receivedNotices = []*pgconn.Notice{}
}
assert.Fail(t, "Received notices do not match expected notices",
"Expected %d notices, but received %d. Expected: %v, Received: %v",
len(assertion.ExpectedNotices), len(receivedNotices), assertion.ExpectedNotices, receivedNotices)
}
}
}
})
}
}
func copyFromStdin(t *testing.T, conn *pgx.Conn, query string, filename string) {
filePath := filepath.Join("testdata", filename)
file, err := os.Open(filePath)
if err != nil {
t.Fatalf("Failed to open file: %v", err)
}
defer file.Close()
reader := bufio.NewReader(file)
_, err = conn.PgConn().CopyFrom(context.Background(), reader, query)
require.NoError(t, err)
}
// RunScripts runs the given collection of scripts. This normalizes all rows before comparing them.
func RunScripts(t *testing.T, scripts []ScriptTest) {
runScripts(t, scripts, true)
}
// RunScriptsWithoutNormalization runs the given collection of scripts, without normalizing any rows.
func RunScriptsWithoutNormalization(t *testing.T, scripts []ScriptTest) {
runScripts(t, scripts, false)
}
// runScripts is the implementation of both RunScripts and RunScriptsWithoutNormalization.
func runScripts(t *testing.T, scripts []ScriptTest, normalizeRows bool) {
// First, we'll run through the scripts to check for the Focus variable. If it's true, then append it to the new slice.
focusScripts := make([]ScriptTest, 0, len(scripts))
for _, script := range scripts {
if script.Focus {
// If this is running in GitHub Actions, then we'll panic, because someone forgot to disable it before committing
if _, ok := os.LookupEnv("GITHUB_ACTION"); ok {
panic(fmt.Sprintf("The script `%s` has Focus set to `true`. GitHub Actions requires that "+
"all tests are run, which Focus circumvents, leading to this error. Please disable Focus on "+
"all tests.", script.Name))
}
focusScripts = append(focusScripts, script)
}
}
// If we have scripts with Focus set, then we replace the normal script slice with the new slice.
if len(focusScripts) > 0 {
scripts = focusScripts
}
for _, script := range scripts {
RunScript(t, script, normalizeRows)
}
}
func ptr[T any](val T) *T {
return &val
}
var testServerLogLevel = "warn"
func init() {
if logLevel, ok := os.LookupEnv("TEST_SERVER_LOG_LEVEL"); ok {
testServerLogLevel = logLevel
}
}
// CreateServer creates a server with the given database, returning a connection to the server. The server will close
// when the connection is closed (or loses its connection to the server). The accompanying [svcs.Controller] may be used
// to wait until the server has closed.
func CreateServer(t *testing.T, database string) (context.Context, *Connection, *svcs.Controller) {
port, err := sql.GetEmptyPort()
require.NoError(t, err)
return CreateServerWithPort(t, database, port)
}
// CreateServerWithPort creates a server with the given database and port, returning a connection to the server. The server will close
// when the connection is closed (or loses its connection to the server). The accompanying [svcs.Controller] may be used
// to wait until the server has closed.
func CreateServerWithPort(t *testing.T, database string, port int) (context.Context, *Connection, *svcs.Controller) {
require.NotEmpty(t, database)
controller, err := dserver.RunInMemory(&servercfg.DoltgresConfig{
DoltgresConfig: cfgdetails.DoltgresConfig{
ListenerConfig: &cfgdetails.DoltgresListenerConfig{
PortNumber: &port,
HostStr: &serverHost,
},
LogLevelStr: &testServerLogLevel,
},
}, dserver.NewListener)
require.NoError(t, err)
auth.ClearDatabase()
fmt.Printf("port is %d\n", port)
ctx := context.Background()
connection := newTestDatabaseConnection(t, ctx, database, serverHost, port)
return ctx, connection, controller
}
// CreateServerLocalWithPort creates a server using the local file system at [os.TempDir]. A Connection is returned to
// |database| at 127.0.0.1:|port|. The server will close when the connection is closed or lost. The returned
// [svcs.Controller] may be used to wait for the server to stop.
func CreateServerLocalWithPort(t *testing.T, database string, port int) (context.Context, *Connection, *svcs.Controller) {
// We avoid using [T.TempDir] because it results in a file lock conflict on Windows. [T.TempDir] registers a
// [T.Cleanup] function that runs without checking the [svcs.Controller] and it cannot be overwritten.
// TODO(elianddb): Setup an optional [T.Cleanup] function for the temporary directory. Our default setup for now is
// preferable for debugging the database after a failure.
// t.Name() contains "/" for subtests, which os.MkdirTemp rejects as a path separator in the pattern.
safeName := strings.ReplaceAll(t.Name(), "/", "_")
dbDir, err := os.MkdirTemp(os.TempDir(), safeName)
require.NoError(t, err)
fileSys, err := filesys.LocalFilesysWithWorkingDir(dbDir)
require.NoError(t, err)
ctx := context.Background()
doltEnv := env.Load(ctx, env.GetCurrentUserHomeDir, fileSys, doltdb.LocalDirDoltDB, dserver.Version)
controller, err := dserver.RunOnDisk(ctx, &servercfg.DoltgresConfig{
DoltgresConfig: cfgdetails.DoltgresConfig{
ListenerConfig: &cfgdetails.DoltgresListenerConfig{
PortNumber: &port,
HostStr: &serverHost,
},
LogLevelStr: &testServerLogLevel,
},
}, doltEnv)
require.NoError(t, err)
auth.ClearDatabase()
fmt.Printf("port is %d\n", port)
connection := newTestDatabaseConnection(t, ctx, database, serverHost, port)
return ctx, connection, controller
}
// newTestDatabaseConnection returns a Connection to the test |database| at |host|:|port|. If the |database| provided
// does not exist, it will be automatically created.
func newTestDatabaseConnection(t *testing.T, ctx context.Context, database, host string, port int) *Connection {
const connectionUrlFmt = "postgres://postgres:password@%s:%d/%s?DateStyle=ISO%%2C%%20MDY"
func() {
var conn *pgx.Conn
var err error
// Connections can happen before the server has a chance to grab the port so we retry.
for range 3 {
conn, err = pgx.Connect(ctx, fmt.Sprintf(connectionUrlFmt, host, port, ""))
if err == nil {
break
}
time.Sleep(time.Second)
}
require.NoError(t, err)
_, err = conn.Exec(ctx, fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", database))
require.NoError(t, err)
defer require.NoError(t, conn.Close(ctx))
}()
config, err := pgx.ParseConfig(fmt.Sprintf(connectionUrlFmt, host, port, database))
require.NoError(t, err)
config.OnNotice = func(conn *pgconn.PgConn, notice *pgconn.Notice) {
receivedNotices = append(receivedNotices, notice)
}
// pgx v5.9.1+ skips Describe(portal) on statement-cache hits, so stale field descriptions
// from before a DDL change (e.g. DROP COLUMN) cause a field-count/value-count mismatch and
// panic in rows.Values(). DescribeExec re-describes every query, keeping schema info fresh.
// It also preserves binary wire format (needed for record type decoding).
config.DefaultQueryExecMode = pgx.QueryExecModeDescribeExec
conn, err := pgx.ConnectConfig(ctx, config)
require.NoError(t, err)
// Ping tests that Doltgres can handle empty queries, and makes sure connection is alive.
require.NoError(t, conn.Ping(ctx))
return &Connection{
Default: conn,
Current: conn,
}
}
// ReadRows reads all of the given rows into a slice, then closes the rows. If `normalizeRows` is true, then the rows
// will be normalized such that all integers are int64, etc. Normalization does not affect the raw returned bytes.
func ReadRows(rows pgx.Rows, normalizeRows bool) (readRows []sql.Row, readRawRows [][][]byte, err error) {
defer func() {
err = goerrors.Join(err, rows.Err())
}()
var slices []sql.Row
var rawSlices [][][]byte
for rows.Next() {
var rawSlice [][]byte
for _, rawValue := range rows.RawValues() {
rawSlice = append(rawSlice, append([]byte{}, rawValue...))
}
rawSlices = append(rawSlices, rawSlice)
row, err := rows.Values()
if err != nil {
return nil, nil, err
}
slices = append(slices, row)
}
return NormalizeRows(rows.FieldDescriptions(), slices, normalizeRows), rawSlices, nil
}
// NormalizeRows normalizes each value's type within each row, as the tests only want to compare values. Returns a new
// set of rows in the same order.
func NormalizeRows(fds []pgconn.FieldDescription, rows []sql.Row, normalize bool) []sql.Row {
newRows := make([]sql.Row, len(rows))
for i := range rows {
newRows[i] = NormalizeRow(fds, rows[i], normalize)
}
return newRows
}
// NormalizeRow normalizes each value's type, as the tests only want to compare values.
// Returns a new row.
func NormalizeRow(fds []pgconn.FieldDescription, row sql.Row, normalize bool) sql.Row {
if len(row) == 0 {
return nil
}
newRow := make(sql.Row, len(row))
for i := range row {
dt, ok := types.IDToBuiltInDoltgresType[id.Type(id.Cache().ToInternal(fds[i].DataTypeOID))]
if !ok {
// try using text type
dt = types.Text
}
newRow[i] = NormalizeValToString(dt, row[i])
if normalize {
newRow[i] = NormalizeIntsAndFloats(newRow[i])
}
}
return newRow
}
// NormalizeExpectedRow normalizes each value's type, as the tests only want to compare values. Returns a new row.
func NormalizeExpectedRow(fds []pgconn.FieldDescription, rows []sql.Row) []sql.Row {
newRows := make([]sql.Row, len(rows))
for ri, row := range rows {
if len(row) == 0 {
newRows[ri] = nil
} else if len(row) != len(fds) {
// Return if the expected row count does not match the field description count, we'll error elsewhere
return rows
} else {
newRow := make(sql.Row, len(row))
for i := range row {
dt, ok := types.IDToBuiltInDoltgresType[id.Type(id.Cache().ToInternal(fds[i].DataTypeOID))]
if !ok {
// try using text type
dt = types.Text
}
if dt.ID == types.Json.ID && row[i] != nil {
newRow[i] = UnmarshalAndMarshalJsonString(row[i].(string))
} else if dt.IsArrayType() && dt.ArrayBaseType().ID == types.Json.ID {
v, err := dt.IoInput(nil, row[i].(string))
if err != nil {
panic(err)
}
arr := v.([]any)
newArr := make([]any, len(arr))
for j, el := range arr {
switch e := el.(type) {
case string:
newArr[j] = UnmarshalAndMarshalJsonString(e)
case sql.JSONWrapper:
iface, err := e.ToInterface(context.Background())
if err != nil {
panic(err)
}
b, err := json.Marshal(iface)
if err != nil {
panic(err)
}
newArr[j] = string(b)
default:
newArr[j] = el
}
}
ret, err := dt.FormatValue(newArr)
if err != nil {
panic(err)
}
newRow[i] = ret
} else if dt.ID == types.Date.ID {
newRow[i] = row[i]
if row[i] != nil {
if t, _, err := pgtree.ParseDTimestampTZ(nil, row[i].(string), pgtree.TimeFamilyPrecisionToRoundDuration(6), time.UTC); err == nil {
newRow[i] = functions.FormatDateTimeWithBC(t.Time.UTC(), "2006-01-02", dt.ID == types.TimestampTZ.ID)
}
}
} else if dt.ID == types.Timestamp.ID || dt.ID == types.TimestampTZ.ID {
newRow[i] = row[i]
if row[i] != nil {
if t, _, err := pgtree.ParseDTimestampTZ(nil, row[i].(string), pgtree.TimeFamilyPrecisionToRoundDuration(6), time.UTC); err == nil {
newRow[i] = functions.FormatDateTimeWithBC(t.Time.UTC(), "2006-01-02 15:04:05.999999", dt.ID == types.TimestampTZ.ID)
}
}
} else {
newRow[i] = NormalizeIntsAndFloats(row[i])
}
}
newRows[ri] = newRow
}
}
return newRows
}
// UnmarshalAndMarshalJsonString is used to normalize expected json type value to compare the actual value.
// JSON type value is in string format, and since Postrges JSON type preserves the input string if valid,
// it cannot be compared to the returned map as json.Marshal method space padded key value pair.
// To allow result matching, we unmarshal and marshal the expected string. This causes missing check
// for the identical format as the input of the json string.
func UnmarshalAndMarshalJsonString(val string) string {
var decoded any
err := json.Unmarshal([]byte(val), &decoded)
if err != nil {
panic(err)
}
ret, err := json.Marshal(decoded)
if err != nil {
panic(err)
}
return string(ret)
}
// NormalizeValToString normalizes values into types that can be compared.
// JSON types, any pg types and time and decimal type values are converted into string value.
// |normalizeNumeric| defines whether to normalize Numeric values into either Numeric type or string type.
// There are an infinite number of ways to represent the same value in-memory,
// so we must at least normalize Numeric values.
func NormalizeValToString(dt *types.DoltgresType, v any) any {
if v == nil {
return nil
}
switch dt.ID {
case types.Json.ID:
jsonBytes, err := json.Marshal(v)
if err != nil {
panic(err)
}
return string(jsonBytes)
case types.JsonB.ID:
// JSONB normalizes JSON with spaces after ':' and ',' (PostgreSQL JSONB format)
s, err := gmstypes.JSONDocument{Val: v}.JSONString()
if err != nil {
panic(err)
}
return s
case types.InternalChar.ID:
if v == nil {
return nil
}
var b []byte
if v.(int32) == 0 {
b = []byte{}
} else {
b = []byte{uint8(v.(int32))}
}
val, err := dt.FormatValue(string(b))
if err != nil {
panic(err)
}
return val
case types.Interval.ID, types.Uuid.ID:
// These values need to be normalized into the appropriate types
// before being converted to string type using the Doltgres
// IoOutput method.
if v == nil {
return nil
}
tVal, err := dt.FormatValue(NormalizeVal(dt, v))
if err != nil {
panic(err)
}
return tVal
case types.Date.ID:
if v == nil {
return nil
}
return functions.FormatDateTimeWithBC(v.(time.Time), "2006-01-02", false)
case types.Timestamp.ID, types.TimestampTZ.ID:
if v == nil {
return nil
}
return functions.FormatDateTimeWithBC(v.(time.Time).UTC(), "2006-01-02 15:04:05.999999", dt.ID == types.TimestampTZ.ID)
}
switch val := v.(type) {
case bool:
if val {
return "t"
} else {
return "f"
}
case pgtype.Time:
return timeofday.TimeOfDay(val.Microseconds).String()
case []any:
if dt.IsArrayType() {
return NormalizeArrayType(dt, val)
}
}
return v
}
// NormalizeArrayType normalizes array types by normalizing its elements first,
// then to a string using the type IoOutput method.
func NormalizeArrayType(dt *types.DoltgresType, arr []any) any {
newVal := make([]any, len(arr))
for i, el := range arr {
newVal[i] = NormalizeVal(dt.ArrayBaseType(), el)
}
baseType := dt.ArrayBaseType()
if baseType.ID == types.Bool.ID {
sqlVal, err := dt.SQL(sql.NewEmptyContext(), nil, newVal)
if err != nil {
panic(err)
}
return sqlVal.ToString()
} else {
ret, err := dt.FormatValue(newVal)
if err != nil {
panic(err)
}
return ret
}
}
// NormalizeVal normalizes values to the Doltgres type expects, so it can be used to
// convert the values using the given Doltgres type. This is used to normalize array
// types as the type conversion expects certain type values.
func NormalizeVal(dt *types.DoltgresType, v any) any {
switch dt.ID {
case types.Json.ID:
str, err := json.Marshal(v)
if err != nil {
panic(err)
}
return string(str)
case types.JsonB.ID:
return gmstypes.JSONDocument{Val: v}
case types.Oid.ID, types.Regclass.ID, types.Regproc.ID, types.Regtype.ID:
if uval, ok := v.(uint32); ok {
if internalID := id.Cache().ToInternal(uval); internalID.IsValid() {
return internalID
}
return id.NewOID(uval).AsId()
}
}
switch val := v.(type) {
case pgtype.Numeric:
if val.NaN {
return math.NaN()
} else if val.InfinityModifier != pgtype.Finite {
return math.Inf(int(val.InfinityModifier))
} else if !val.Valid {
return nil
} else {
return apd.New(val.Int.Int64(), val.Exp)
}
case pgtype.Time:
// This value type is used for TIME type.
return timeofday.FromInt(val.Microseconds)
case pgtype.Interval:
// This value type is used for INTERVAL type.
return duration.MakeDuration(val.Microseconds*functions.NanosPerMicro, int64(val.Days), int64(val.Months))
case [16]byte:
// This value type is used for UUID type.
u, err := uuid.FromBytes(val[:])
if err != nil {
panic(err)
}
return u
case []any:
baseType := dt
if baseType.IsArrayType() {
baseType = baseType.ArrayBaseType()
}
newVal := make([]any, len(val))
for i, el := range val {
newVal[i] = NormalizeVal(baseType, el)
}
return newVal
}
return v
}
// NormalizeIntsAndFloats normalizes all int and float types
// to int64 and float64, respectively.
func NormalizeIntsAndFloats(v any) any {
switch val := v.(type) {
case int:
return int64(val)
case int8:
return int64(val)
case int16:
return int64(val)
case int32:
return int64(val)
case uint:
return int64(val)
case uint8:
return int64(val)
case uint16:
return int64(val)
case uint32:
return int64(val)
case uint64:
// PostgreSQL does not support an uint64 type, so we can always convert this to an int64 safely.
return int64(val)
case float32:
return float64(val)
case []any:
for i := range val {
val[i] = NormalizeIntsAndFloats(val[i])
}
return val
default:
return val
}
}
// Numeric creates a numeric value from a string.
func Numeric(str string) pgtype.Numeric {
numeric := pgtype.Numeric{}
if err := numeric.Scan(str); err != nil {
panic(err)
}
return numeric
}
// Timestamp is a helper function to convert timestamp strings to pgtype.Timestamp instances. If
// the string cannot be converted, this function will panic.
func Timestamp(timestampStr string) (ret pgtype.Timestamp) {
t, err := time.Parse("2006-01-02 15:04:05", timestampStr)
if err != nil {
panic("invalid timestamp format: " + err.Error())
}
if err := ret.Scan(t); err != nil {
panic("failed to set pgtype.Timestamp: " + err.Error())
}
return ret
}
// Date is a helper function to convert date strings to pgtype.Date instances. If the string
// cannot be converted, this function will panic.
func Date(dateStr string) (d pgtype.Date) {
t, err := time.Parse("2006-01-02", dateStr)
if err != nil {
panic("invalid date format: " + err.Error())
}
if err := d.Scan(t); err != nil {
panic("failed to set pgtype.Date: " + err.Error())
}
return d
}
// UUID is a helper function to convert UUID strings to pgtype.UUID instances. If the string
// cannot be converted, this function will panic.
func UUID(s string) (u pgtype.UUID) {
if err := u.Scan(s); err != nil {
panic(err)
}
return u
}
// Connect replaces the Current connection with a new one, using the given username and password. If the username is
// empty, then the default connection is used. If the username and password match the existing connection, then no new
// connection is made.
func (conn *Connection) Connect(ctx context.Context, username string, password string) error {
// Reuse the existing connection if it's the same username and password
if username == conn.Username && password == conn.Password && conn.Current != nil {
return nil
}
// Username or password has changed, so we'll close the Current connection only if it's not the default
if conn.Username != "" && conn.Current != nil {
_ = conn.Current.Close(ctx)
}
conn.Username = username
conn.Password = password
if username == "" {
conn.Current = conn.Default
return nil
} else {
var err error
config := conn.Default.Config()
conn.Current, err = pgx.Connect(ctx, fmt.Sprintf("postgres://%s:%s@127.0.0.1:%d/%s", username, password, config.Port, config.Database))
return err
}
}
// Exec calls Exec on the current connection.
func (conn *Connection) Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) {
if conn.Current == nil {
return pgconn.CommandTag{}, errors.New("EXEC: current connection is nil")
}
return conn.Current.Exec(ctx, sql, args...)
}
// Query calls Query on the current connection.
func (conn *Connection) Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error) {
if conn.Current == nil {
return nil, errors.New("QUERY: current connection is nil")
}
return conn.Current.Query(ctx, sql, args...)
}
// Close closes the connections.
func (conn *Connection) Close(ctx context.Context) {
if conn.Default != nil {
_ = conn.Default.Close(ctx)
}
if conn.Current != nil && conn.Current != conn.Default {
_ = conn.Current.Close(ctx)
}
conn.Default = nil
conn.Current = nil
}
File diff suppressed because it is too large Load Diff
+239
View File
@@ -0,0 +1,239 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
// TestGettingStartedGuide tests that the steps in the Doltgres Getting Started Guide work correctly.
// https://github.com/dolthub/doltgresql?tab=readme-ov-file#getting-started
func TestGettingStartedGuide(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Doltgres Getting Started Guide",
SetUpScript: []string{
"create table employees (\n id int8,\n last_name text,\n first_name text,\n primary key(id));",
"create table teams (\n id int8,\n team_name text,\n primary key(id));",
"create table employees_teams(\n team_id int8,\n employee_id int8,\n primary key(team_id, employee_id),\n foreign key (team_id) references teams(id),\n foreign key (employee_id) references employees(id));",
},
Assertions: []ScriptTestAssertion{
// Make a Dolt Commit
{
Query: "select * from dolt.status;",
Expected: []sql.Row{
{"public.employees", "f", "new table"},
{"public.employees_teams", "f", "new table"},
{"public.teams", "f", "new table"},
},
},
{
Query: "select dolt_add('teams', 'employees', 'employees_teams');",
Expected: []sql.Row{{"{0}"}},
},
{
Query: "select * from dolt.status;",
Expected: []sql.Row{
{"public.employees", "t", "new table"},
{"public.employees_teams", "t", "new table"},
{"public.teams", "t", "new table"},
},
},
{
Query: "select length(dolt_commit('-m', 'Created initial schema')::text);",
Expected: []sql.Row{{34}},
},
{
// TODO: employees_teams is still marked as modified even though we staged and committed it. The diff
// in the working set shows a schema change of adding the FK references. For now, we reset to
// remove this artifact and prevent it from showing up in later test assertions, but this can
// be removed once the issue is fixed.
// https://github.com/dolthub/doltgresql/issues/734
Query: "select dolt_reset('--hard');",
Expected: []sql.Row{{"{0}"}},
},
{
Query: "select * from dolt.status;",
Expected: []sql.Row{},
},
{
Query: "select count(*) from dolt.log;",
Expected: []sql.Row{{3}},
},
// Insert Some Data
{
Query: "insert into employees values (0, 'Sehn', 'Tim'), (1, 'Hendriks', 'Brian'), (2, 'Son','Aaron'), (3, 'Fitzgerald', 'Brian');",
Expected: []sql.Row{},
},
{
Query: "insert into teams values (0, 'Engineering'), (1, 'Sales');",
Expected: []sql.Row{},
},
{
Query: "insert into employees_teams(employee_id, team_id) values (0,0), (1,0), (2,0), (0,1), (3,1);",
Expected: []sql.Row{},
},
{
Query: `select first_name, last_name, team_name from employees
join employees_teams on (employees.id=employees_teams.employee_id)
join teams on (teams.id=employees_teams.team_id)
where team_name='Engineering';`,
Expected: []sql.Row{
{"Tim", "Sehn", "Engineering"},
{"Brian", "Hendriks", "Engineering"},
{"Aaron", "Son", "Engineering"},
},
},
{
Query: "select * from employees_teams where employee_id='0' and team_id='1';",
Expected: []sql.Row{{1, 0}},
},
// Examine the Diff
{
Query: "select * from dolt.status order by table_name;",
Expected: []sql.Row{
{"public.employees", "f", "modified"},
{"public.employees_teams", "f", "modified"},
{"public.teams", "f", "modified"},
},
},
{
Query: "select to_last_name, to_first_name, to_id, to_commit, from_last_name, from_first_name, from_id, diff_type from dolt_diff_employees;",
Expected: []sql.Row{
{"Sehn", "Tim", 0, "WORKING", nil, nil, nil, "added"},
{"Hendriks", "Brian", 1, "WORKING", nil, nil, nil, "added"},
{"Son", "Aaron", 2, "WORKING", nil, nil, nil, "added"},
{"Fitzgerald", "Brian", 3, "WORKING", nil, nil, nil, "added"},
},
},
{
Query: "select length(dolt_commit('-am', 'Populated tables with data')::text);",
Expected: []sql.Row{{34}},
},
{
Query: "select * from dolt.status order by table_name;",
Expected: []sql.Row{},
},
{
Query: "select message from dolt.log;",
Expected: []sql.Row{
{"Populated tables with data"},
{"Created initial schema"},
{"CREATE DATABASE"},
{"Initialize data repository"},
},
},
{
Query: "select table_name, message, data_change, schema_change from dolt.diff order by date desc, table_name;",
Expected: []sql.Row{
{"public.employees", "Populated tables with data", "t", "f"},
{"public.employees_teams", "Populated tables with data", "t", "f"},
{"public.teams", "Populated tables with data", "t", "f"},
{"public.employees", "Created initial schema", "f", "t"},
{"public.employees_teams", "Created initial schema", "f", "t"},
{"public.teams", "Created initial schema", "f", "t"},
},
},
// Oh no! I made a mistake.
{
Query: "drop table employees_teams;",
Expected: []sql.Row{},
},
{
Query: "select count(*) from employees_teams;",
ExpectedErr: "table not found: employees_teams",
},
{
Query: "select dolt_reset('--hard');",
Expected: []sql.Row{{"{0}"}},
},
{
Query: "select count(*) from employees_teams;",
Expected: []sql.Row{{5}},
},
// Make changes on a branch
{
Query: "select dolt_checkout('-b','modifications');",
Expected: []sql.Row{{"{0,\"Switched to branch 'modifications'\"}"}},
},
{
Query: "update employees SET first_name='Timothy' where first_name='Tim';",
Expected: []sql.Row{},
},
{
Query: "insert INTO employees (id, first_name, last_name) values (4,'Daylon', 'Wilkins');",
Expected: []sql.Row{},
},
{
Query: "insert into employees_teams(team_id, employee_id) values (0,4);",
Expected: []sql.Row{},
},
{
Query: "delete from employees_teams where employee_id=0 and team_id=1;",
Expected: []sql.Row{},
},
{
Query: "select length(dolt_commit('-am', 'Modifications on a branch')::text);",
Expected: []sql.Row{{34}},
},
{
Query: "select dolt_checkout('main');",
Expected: []sql.Row{{"{0,\"Switched to branch 'main'\"}"}},
},
{
Query: "select name, latest_commit_message from dolt.branches;",
Expected: []sql.Row{
{"main", "Populated tables with data"},
{"modifications", "Modifications on a branch"},
},
},
{
Query: "select active_branch();",
Expected: []sql.Row{{"main"}},
},
{
Query: "select * from employees;",
Expected: []sql.Row{{0, "Sehn", "Tim"}, {1, "Hendriks", "Brian"}, {2, "Son", "Aaron"}, {3, "Fitzgerald", "Brian"}},
},
{
Query: "select * from employees as of 'modifications';",
Expected: []sql.Row{{0, "Sehn", "Timothy"}, {1, "Hendriks", "Brian"}, {2, "Son", "Aaron"}, {3, "Fitzgerald", "Brian"}, {4, "Wilkins", "Daylon"}},
},
{
Query: `select to_id, to_last_name, to_first_name, to_commit,
from_id, from_last_name, from_first_name, from_commit, diff_type
from dolt_diff('main', 'modifications', 'employees');`,
Expected: []sql.Row{
{0, "Sehn", "Timothy", "modifications", 0, "Sehn", "Tim", "main", "modified"},
{4, "Wilkins", "Daylon", "modifications", nil, nil, nil, "main", "added"},
},
},
// Make a schema change on another branch
// TODO: Most ALTER TABLE statements aren't supported yet
{
Query: "select dolt_checkout('-b', 'schema_changes');",
Expected: []sql.Row{{"{0,\"Switched to branch 'schema_changes'\"}"}},
},
},
},
})
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestGroupBy(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Basic order by/group by cases",
SetUpScript: []string{
"create table members (id bigint primary key, team text);",
"insert into members values (3,'red'), (4,'red'),(5,'orange'),(6,'orange'),(7,'orange'),(8,'purple');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select team as f from members order by id, f",
Expected: []sql.Row{{"red"}, {"red"}, {"orange"}, {"orange"}, {"orange"}, {"purple"}},
},
{
Query: "SELECT team, COUNT(*) FROM members GROUP BY team ORDER BY 2",
Expected: []sql.Row{
{"purple", int64(1)},
{"red", int64(2)},
{"orange", int64(3)},
},
},
{
Query: "SELECT team, COUNT(*) FROM members GROUP BY 1 ORDER BY 2",
Expected: []sql.Row{
{"purple", int64(1)},
{"red", int64(2)},
{"orange", int64(3)},
},
},
{
Query: "SELECT team, COUNT(*) FROM members GROUP BY team ORDER BY columndoesnotexist",
ExpectedErr: "not be found",
},
{
Query: "SELECT DISTINCT t1.id as id FROM members AS t1 JOIN members AS t2 ON t1.id = t2.id WHERE t2.id > 0 ORDER BY t1.id",
Expected: []sql.Row{{3}, {4}, {5}, {6}, {7}, {8}},
},
{
Query: "SELECT id as alias1, (SELECT alias1+1 group by alias1 having alias1 > 0) FROM members where id < 6;",
Expected: []sql.Row{{3, 4}, {4, 5}, {5, 6}},
},
{
Query: "SELECT id, (SELECT UPPER(team) having id > 3) as upper_team FROM members where id < 6;",
Expected: []sql.Row{{3, nil}, {4, "RED"}, {5, "ORANGE"}},
},
{
Query: "SELECT id, (SELECT -1 as id having id < 10) as upper_team FROM members where id < 6;",
Expected: []sql.Row{{3, -1}, {4, -1}, {5, -1}},
},
},
},
})
}
+675
View File
@@ -0,0 +1,675 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dolthub/doltgresql/testing/dumps"
)
// TestImportingDumps are regression tests against dumps taken from various sources.
func TestImportingDumps(t *testing.T) {
t.Skip("The majority fail for now")
RunImportTests(t, []ImportTest{
{
Name: "Scrubbed-1",
SetUpScript: []string{
"CREATE USER behfjgnf WITH SUPERUSER PASSWORD 'password';",
},
SkipQueries: []string{"CREATE UNIQUE INDEX dawkmezfehakyikllr"},
SQLFilename: "scrubbed-1.sql",
},
{
Name: "A-lang209/Salon-Appointment-Scheduler",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "A-lang209_Salon-Appointment-Scheduler.sql",
},
{
Name: "Abhishek842000/DB-Performance-Comparator",
SQLFilename: "Abhishek842000_DB-Performance-Comparator.sql",
},
{
Name: "AlexTransit/venderctl",
SQLFilename: "AlexTransit_venderctl.sql",
},
{
SetUpScript: []string{
"CREATE USER testuser WITH SUPERUSER PASSWORD 'password';",
},
Name: "AliiAhmadi/PostScan",
SQLFilename: "AliiAhmadi_PostScan.sql",
},
{
SetUpScript: []string{
"CREATE USER freecodecamp WITH SUPERUSER PASSWORD 'password';",
},
Name: "amittannagit/World-Cup-database-project-files",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "amittannagit_World-Cup-database-project-files.sql",
},
{
Name: "Ansh-Rathod/Musive-backend-2.0",
SQLFilename: "Ansh-Rathod_Musive-backend-2.0.sql",
},
{
SetUpScript: []string{
"CREATE USER app_admin WITH SUPERUSER PASSWORD 'password';",
"CREATE USER analytics_viewer WITH SUPERUSER PASSWORD 'password';",
"CREATE USER content_manager WITH SUPERUSER PASSWORD 'password';",
},
Name: "artygg/Data-Processing-Goida",
SQLFilename: "artygg_Data-Processing-Goida.sql",
},
{
Name: "bartr/agency",
SQLFilename: "bartr_agency.sql",
},
{
SetUpScript: []string{
"CREATE USER edm WITH SUPERUSER PASSWORD 'password';",
},
Name: "bclynch/edmflare",
SQLFilename: "bclynch_edmflare.sql",
},
{
Name: "Billoxinogen18/ar_backend",
SQLFilename: "Billoxinogen18_ar_backend.sql",
},
{
SetUpScript: []string{
"CREATE USER crisisresolver_visitor WITH SUPERUSER PASSWORD 'password';",
},
Name: "blacktscoder/CrisisSolver",
SQLFilename: "blacktscoder_CrisisSolver.sql",
},
{
Name: "Boluwatife-AJB/backend-in-node",
SQLFilename: "Boluwatife-AJB_backend-in-node.sql",
},
{
Name: "bonf1re/campis",
SQLFilename: "bonf1re_campis.sql",
},
{
Name: "by-zah/universityScheduleBot",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "by-zah_universityScheduleBot.sql",
},
{
Name: "cardox6/pagila",
SQLFilename: "cardox6_pagila.sql",
},
{
Name: "Chris-Merced/Classic-Messenger-App-Backend",
SQLFilename: "Chris-Merced_Classic-Messenger-App-Backend.sql",
},
{
Name: "cipherstash/pyconau2024-ctf",
SQLFilename: "cipherstash_pyconau2024-ctf.sql",
},
{
Name: "Clar17y/Football-Events",
SQLFilename: "Clar17y_Football-Events.sql",
},
{
SetUpScript: []string{
"CREATE USER risk WITH SUPERUSER PASSWORD 'password';",
},
Name: "CollegeFootballRisk/Risk",
SQLFilename: "CollegeFootballRisk_Risk.sql",
},
{
Name: "CONABIO/inaturalist_snmb",
SQLFilename: "CONABIO_inaturalist_snmb.sql",
},
{
Name: "CravingCrates/AnkiCollab-Backend",
SQLFilename: "CravingCrates_AnkiCollab-Backend.sql",
},
{
Name: "cskerritt/lifeplan-genius",
SQLFilename: "cskerritt_lifeplan-genius.sql",
},
{
Name: "dbarrera98/proyecto-informa",
SQLFilename: "dbarrera98_proyecto-informa.sql",
},
{
Name: "dennis-campos-11/xg90_app",
SQLFilename: "dennis-campos-11_xg90_app.sql",
},
{
Name: "DmitryAntipin151002/Diplom",
SQLFilename: "DmitryAntipin151002_Diplom.sql",
},
{
Name: "Dmitrytsg/onectest",
SQLFilename: "Dmitrytsg_onectest.sql",
},
{
Name: "DRON12261/EduVault",
SQLFilename: "DRON12261_EduVault.sql",
},
{
Name: "DTOcean/dtocean-database",
SQLFilename: "DTOcean_dtocean-database.sql",
},
{
Name: "EdwinRo121/ParcialApi",
SQLFilename: "EdwinRo121_ParcialApi.sql",
},
{
Name: "Enesuygurs/steamcafe",
SQLFilename: "Enesuygurs_steamcafe.sql",
},
{
Name: "erlitx/sql_final",
SQLFilename: "erlitx_sql_final.sql",
},
{
SetUpScript: []string{
`CREATE USER "shop-admin" WITH SUPERUSER PASSWORD 'password';`,
},
Name: "ExposedCat/cashiers-in-shop",
SQLFilename: "ExposedCat_cashiers-in-shop.sql",
},
{
Name: "falling-fruit/falling-fruit",
SQLFilename: "falling-fruit_falling-fruit.sql",
},
{
Name: "fanfanfw/bdt_rest-api-scraping-result",
SQLFilename: "fanfanfw_bdt_rest-api-scraping-result.sql",
},
{
Name: "fn-bucket/fnb-nuxt-postgraphile",
SQLFilename: "fn-bucket_fnb-nuxt-postgraphile.sql",
},
{
Name: "Freeztyle17/Neoflex_1",
SQLFilename: "Freeztyle17_Neoflex_1.sql",
},
{
Name: "gabrundo/Progetto-Basi-Dati",
SQLFilename: "gabrundo_Progetto-Basi-Dati.sql",
},
{
Name: "gnsnghm/cms",
SQLFilename: "gnsnghm_cms.sql",
},
{
Name: "gsdnMartin/PIDAP",
SQLFilename: "gsdnMartin_PIDAP.sql",
},
{
SetUpScript: []string{
`CREATE USER ssouser WITH SUPERUSER PASSWORD 'password';`,
},
Name: "HalfCoke/blog_img",
SQLFilename: "HalfCoke_blog_img.sql",
},
{
Name: "HarukaMa/aleo-explorer",
SQLFilename: "HarukaMa_aleo-explorer.sql",
},
{
SetUpScript: []string{
`CREATE USER rustyz WITH SUPERUSER PASSWORD 'password';`,
},
Name: "heydabop/rustyz",
SQLFilename: "heydabop_rustyz.sql",
},
{
Name: "HugoTZC/OASA",
SQLFilename: "HugoTZC_OASA.sql",
},
{
Name: "iangow/pg_functions",
SQLFilename: "iangow_pg_functions.sql",
},
{
Name: "ii-habibi/Dental-Clinic",
SQLFilename: "ii-habibi_Dental-Clinic.sql",
},
{
Name: "InnovaTech-Official/LMS",
SQLFilename: "InnovaTech-Official_LMS.sql",
},
{
Name: "jeffchang001/ee-midd",
SQLFilename: "jeffchang001_ee-midd.sql",
},
{
Name: "joaoporto27/Bora-Viajar-BackEnd",
SQLFilename: "joaoporto27_Bora-Viajar-BackEnd.sql",
},
{
Name: "joec05/social-media-app-pgsql",
SQLFilename: "joec05_social-media-app-pgsql.sql",
},
{
Name: "julesd7/collatask",
SQLFilename: "julesd7_collatask.sql",
},
{
Name: "jwalit21/BitmapJoinDatabaseEngine",
SQLFilename: "jwalit21_BitmapJoinDatabaseEngine.sql",
},
{
Name: "KangAbbad/laundry-app",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "KangAbbad_laundry-app.sql",
},
{
SetUpScript: []string{
`CREATE USER "hospitease_admin" WITH SUPERUSER PASSWORD 'password';`,
},
Name: "kapil23jani/hospitease_backend",
SQLFilename: "kapil23jani_hospitease_backend.sql",
},
{
Name: "kentyler/conversationalaiapi",
SQLFilename: "kentyler_conversationalaiapi.sql",
},
{
SetUpScript: []string{
`CREATE USER recruiter;`,
`CREATE USER job_seeker;`,
`CREATE USER employer;`,
},
Name: "kepinskw/db-jobportal",
SQLFilename: "kepinskw_db-jobportal.sql",
},
{
Name: "kirooha/adtech-simple",
SQLFilename: "kirooha_adtech-simple.sql",
},
{
Name: "kjanus03/tsn",
SQLFilename: "kjanus03_tsn.sql",
},
{
Name: "kraftn/queue-server",
SQLFilename: "kraftn_queue-server.sql",
},
{
Name: "linvivian7/fe-react-16-demo",
SQLFilename: "linvivian7_fe-react-16-demo.sql",
},
{
Name: "littlebunch/graphql-rs",
SQLFilename: "littlebunch_graphql-rs.sql",
},
{
Name: "luizantoniocardoso/trabalho-banco-2",
SQLFilename: "luizantoniocardoso_trabalho-banco-2.sql",
},
{
Name: "mintas123/Buddies-API",
SQLFilename: "mintas123_Buddies-API.sql",
},
{
Name: "Mistral-war2ru/PG-connect",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "Mistral-war2ru_PG-connect.sql",
},
{
Name: "mostafacs/ecommerce-microservices-spring-reactive-webflux",
SQLFilename: "mostafacs_ecommerce-microservices-spring-reactive-webflux.sql",
},
{
Name: "MostafaProgramming/100719549",
SQLFilename: "MostafaProgramming_100719549.sql",
},
{
Name: "mraescudeiro/subclue",
SQLFilename: "mraescudeiro_subclue.sql",
},
{
SetUpScript: []string{
`CREATE USER neondb_owner WITH SUPERUSER PASSWORD 'password';`,
},
Name: "mvnp/start-dashboard-v3-backend",
SQLFilename: "mvnp_start-dashboard-v3-backend.sql",
},
{
Name: "NarasimhaProcess/UserTracking",
SQLFilename: "NarasimhaProcess_UserTracking.sql",
},
{
Name: "NathalyHolguin16/Sistema_de_gesti-n_de_Cine",
SQLFilename: "NathalyHolguin16_Sistema_de_gesti-n_de_Cine.sql",
},
{
Name: "NECKER55/supermarket_shop",
SQLFilename: "NECKER55_supermarket_shop.sql",
},
{
Name: "nxtrm/neanote",
SQLFilename: "nxtrm_neanote.sql",
},
{
Name: "nyfagel/klubb",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "nyfagel_klubb.sql",
},
{
Name: "oknosoft/windowbuilder-planning",
SQLFilename: "oknosoft_windowbuilder-planning.sql",
},
{
Name: "openeventdatabase/backend",
SQLFilename: "openeventdatabase_backend.sql",
},
{
Name: "openlawnz/openlawnz-data-processor",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "openlawnz_openlawnz-data-processor.sql",
},
{
Name: "oslabs-beta/ditto",
SQLFilename: "oslabs-beta_ditto.sql",
},
{
Name: "paulshriner/fcc-rd-cert",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "paulshriner_fcc-rd-cert.sql",
},
{
Name: "qqtati/diplom",
SQLFilename: "qqtati_diplom.sql",
},
{
Name: "riclolsen/json-scada",
SQLFilename: "riclolsen_json-scada.sql",
},
{
Name: "rmarquez123/titans",
SQLFilename: "rmarquez123_titans.sql",
},
{
SetUpScript: []string{
`CREATE USER supabase_admin WITH SUPERUSER PASSWORD 'password';`,
`CREATE USER anon;`,
`CREATE USER authenticated;`,
`CREATE USER service_role;`,
`CREATE USER supabase_auth_admin;`,
`CREATE USER dashboard_user;`,
`CREATE USER readonly;`,
`CREATE USER partner_token_terminal;`,
},
Name: "roboflow/scavenger-hunt",
SQLFilename: "roboflow_scavenger-hunt.sql",
},
{
Name: "RSNA/isn-edge-server-database",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "RSNA_isn-edge-server-database.sql",
},
{
Name: "S0mbre/russtat",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "S0mbre_russtat.sql",
},
{
Name: "slsfi/digital_edition_db",
SQLFilename: "slsfi_digital_edition_db.sql",
},
{
Name: "SoniaMarrocco/Library-Database",
SQLFilename: "SoniaMarrocco_Library-Database.sql",
},
{
Name: "StrangeGoofy/Yota_game",
SQLFilename: "StrangeGoofy_Yota_game.sql",
},
{
Name: "StronglogicSolutions/kserver",
SQLFilename: "StronglogicSolutions_kserver.sql",
},
{
Name: "surgefm/v2land-redstone",
SQLFilename: "surgefm_v2land-redstone.sql",
},
{
Name: "sylvain-guehria/StockShop",
SQLFilename: "sylvain-guehria_StockShop.sql",
},
{
Name: "TaraPadilla/MarketSpring",
SQLFilename: "TaraPadilla_MarketSpring.sql",
},
{
Name: "the-benchmarker/web-frameworks",
SQLFilename: "the-benchmarker_web-frameworks.sql",
},
{
SetUpScript: []string{
`CREATE USER testnet WITH SUPERUSER PASSWORD 'password';`,
`CREATE USER cloudsqladmin WITH SUPERUSER PASSWORD 'password';`,
`CREATE USER cloudsqlsuperuser WITH SUPERUSER PASSWORD 'password';`,
`CREATE USER explorer;`,
`CREATE USER wallet;`,
`CREATE USER jupyter;`,
`CREATE USER robertyan;`,
`CREATE USER public_readonly;`,
`CREATE USER readonly;`,
`CREATE USER partner_token_terminal;`,
},
Name: "theophoric/prisma-near-indexer",
SQLFilename: "theophoric_prisma-near-indexer.sql",
},
{
Name: "Timovski/Co-opMinesweeper",
SQLFilename: "Timovski_Co-opMinesweeper.sql",
},
{
Name: "tpolecat/cofree",
SQLFilename: "tpolecat_cofree.sql",
},
{
Name: "uzurpastor/UniWorks",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "uzurpastor_UniWorks.sql",
},
{
Name: "Vanesor/zenith",
SQLFilename: "Vanesor_zenith.sql",
},
{
Name: "Vinicius02612/sistema_da_associacao",
SQLFilename: "Vinicius02612_sistema_da_associacao.sql",
},
{
Name: "WhoIsKatie/e-Hotels",
Skip: true, // Database creation uses unsupported params then attempts to connect, hangs indefinitely
SQLFilename: "WhoIsKatie_e-Hotels.sql",
},
{
Name: "wolfufu/Hakaton2025Spring",
SQLFilename: "wolfufu_Hakaton2025Spring.sql",
},
{
Name: "Xarpunk/DemExam",
SQLFilename: "Xarpunk_DemExam.sql",
},
{
Name: "yase-search/yase-engine",
SQLFilename: "yase-search_yase-engine.sql",
},
{
Name: "Yesk0/KBTU_Database_24-25",
SQLFilename: "Yesk0_KBTU_Database_24-25.sql",
},
})
}
// TriggerImportBreakpoint exists so that a breakpoint may be set within the function, on the unused Sprintf. This
// function is called whenever a query matches one of the breakpoint queries defined in the import test. This enables us
// to simulate some kind of breakpoint functionality on import queries, which isn't normally possible.
func TriggerImportBreakpoint(breakpointQuery string) {
// It doesn't actually matter what this function is. It's just here so we can set a breakpoint on something.
_ = fmt.Sprintf("__%s", breakpointQuery)
}
// ImportTest is a test for importing SQL dumps.
type ImportTest struct {
Name string
SetUpScript []string
Focus bool
Skip bool
SQLFilename string
// Breakpoints allow for triggering breakpoints when any matching queries are given. A breakpoint must be set within
// TriggerImportBreakpoint for this to work.
Breakpoints []string
// SkipQueries
SkipQueries []string
}
// RunImportTests runs the given ImportTest scripts.
func RunImportTests(t *testing.T, scripts []ImportTest) {
if _, ok := os.LookupEnv("GITHUB_ACTION"); ok {
if _, ok = os.LookupEnv("GITHUB_ACTION_IMPORT_DUMPS"); !ok {
t.Skip("These tests are run in their own dedicated action")
}
}
var psqlCommand string
switch runtime.GOOS {
case "windows":
psqlCommand = "psql.exe"
default:
psqlCommand = "psql"
}
// Check if PSQL runs directly
var outBuffer bytes.Buffer
cmd := exec.Command(psqlCommand, "--version")
cmd.Stdout = &outBuffer
if !assert.NoError(t, cmd.Run()) || !strings.Contains(outBuffer.String(), "PostgreSQL") {
// We could not run PSQL and get the version, so it must not be in the path.
// We'll check if pg_config is in the path and reference the binary directly.
outBuffer.Reset()
cmd = exec.Command("pg_config", "--bindir")
cmd.Stdout = &outBuffer
if !assert.NoError(t, cmd.Run()) {
require.Fail(t, "Postgres is not installed, cannot run tests")
}
psqlCommand = filepath.Join(strings.TrimSpace(outBuffer.String()), psqlCommand)
// pg_config is in the path, so we'll try and run PSQL by directly referencing the binary
outBuffer.Reset()
cmd = exec.Command(psqlCommand, "--version")
cmd.Stdout = &outBuffer
if !assert.NoError(t, cmd.Run()) || !strings.Contains(outBuffer.String(), "PostgreSQL") {
t.Fatalf("PSQL cannot be found at: `%s`", psqlCommand)
}
}
// Grab the folder with the files to import
_, currentFileLocation, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("Unable to find the folder where the files to import are located")
}
dumpsFolder := filepath.Clean(filepath.Join(filepath.Dir(currentFileLocation), "../dumps/"))
// Set whether we're checking Focus-only scripts or not
useFocus := false
for _, script := range scripts {
if script.Focus {
// If this is running in GitHub Actions, then we'll panic, because someone forgot to disable it before committing
if _, ok := os.LookupEnv("GITHUB_ACTION"); ok {
panic(fmt.Sprintf("The script `%s` has Focus set to `true`. GitHub Actions requires that "+
"all tests are run, which Focus circumvents, leading to this error. Please disable Focus on "+
"all tests.", script.Name))
}
useFocus = true
break
}
}
for _, script := range scripts {
if useFocus != script.Focus {
continue
}
RunImportTest(t, script, psqlCommand, dumpsFolder)
}
}
// RunImportTest runs the given ImportTest script.
func RunImportTest(t *testing.T, script ImportTest, psqlCommand string, dumpsFolder string) {
// TODO: handle other dump types, such as those that require pg_restore
t.Run(script.Name, func(t *testing.T) {
// Mark this test as skipped if we have it set
if script.Skip {
t.Skip()
}
// Create the in-memory server that we'll test against
port, err := sql.GetEmptyPort()
require.NoError(t, err)
ctx, conn, controller := CreateServerWithPort(t, "postgres", port)
func() {
defer conn.Close(ctx)
for _, query := range script.SetUpScript {
_, err = conn.Exec(ctx, query)
require.NoError(t, err)
}
}()
defer func() {
controller.Stop()
err := controller.WaitForStop()
require.NoError(t, err)
}()
// Create the message interceptor
var qeChan chan dumps.ImportQueryError
port, qeChan = dumps.InterceptImportMessages(t, dumps.InterceptArgs{
DoltgresPort: port,
SkippedQueries: script.SkipQueries,
BreakpointQueries: script.Breakpoints,
TriggerBreakpoint: TriggerImportBreakpoint,
})
defer close(qeChan)
var allErrors []dumps.ImportQueryError
go func() {
for chanErr := range qeChan {
allErrors = append(allErrors, chanErr)
}
}()
// Run the import
var outBuffer bytes.Buffer
var errBuffer bytes.Buffer
cmd := exec.Command(psqlCommand, fmt.Sprintf("postgresql://postgres:password@localhost:%d/postgres?sslmode=disable", port))
cmd.Stdout = &outBuffer
cmd.Stderr = &errBuffer
targetFile, err := os.Open(filepath.Join(dumpsFolder, "sql", script.SQLFilename))
require.NoError(t, err)
cmd.Stdin = targetFile
require.NoError(t, cmd.Run())
if len(allErrors) > 0 {
t.Logf("COUNT: %d", len(allErrors))
// If we have more than some threshold, then we'll only show the first few for ease of consumption
for i := 0; i < len(allErrors) && i < 10; i++ {
t.Logf("QUERY: %s\nERROR: %s", allErrors[i].Query, allErrors[i].Error)
}
t.FailNow()
}
})
}
File diff suppressed because it is too large Load Diff
+520
View File
@@ -0,0 +1,520 @@
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestInfoSchemaRevisionDb(t *testing.T) {
RunScripts(t, InfoSchemaRevisionDbScripts)
}
var InfoSchemaRevisionDbScripts = []ScriptTest{
{
Name: "info_schema changes with dolt_checkout",
SetUpScript: []string{
"create table t (a int primary key, b int);",
"select dolt_commit('-Am', 'creating table t');",
"select dolt_branch('b2');",
"select dolt_branch('b3');",
"select dolt_checkout('b2');",
"alter table t add column c int;",
"select dolt_commit('-am', 'added column c on branch b2');",
"select dolt_checkout('b3');",
"alter table t add column d int;",
"select dolt_commit('-am', 'added column d on branch b3');",
"select dolt_checkout('main');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select active_branch();",
Expected: []sql.Row{{"main"}},
},
{
Query: "select column_name from information_schema.columns where table_catalog = 'postgres' and table_name = 't' order by 1;",
Expected: []sql.Row{{"a"}, {"b"}},
},
{
Query: "select dolt_checkout('b2');",
SkipResultsCheck: true,
},
{
Query: "select active_branch();",
Expected: []sql.Row{{"b2"}},
},
{
Query: "select column_name from information_schema.columns where table_catalog = 'postgres' and table_name = 't' order by 1;",
Expected: []sql.Row{{"a"}, {"b"}, {"c"}},
},
{
Query: "select dolt_checkout('b3');",
SkipResultsCheck: true,
},
{
Query: "select active_branch();",
Expected: []sql.Row{{"b3"}},
},
{
Query: "select column_name from information_schema.columns where table_catalog = 'postgres' and table_name = 't' order by 1;",
Expected: []sql.Row{{"a"}, {"b"}, {"d"}},
},
},
},
{
Name: "info_schema with detached HEAD",
SetUpScript: []string{
"create table t (a int primary key, b int);",
"select dolt_commit('-Am', 'creating table t');",
"select dolt_branch('b2');",
"select dolt_branch('b3');",
"select dolt_checkout('b2');",
"alter table t add column c int;",
"select dolt_commit('-am', 'added column c on branch b2');",
"select dolt_tag('t2')",
"select dolt_checkout('b3');",
"alter table t add column d int;",
"select dolt_commit('-am', 'added column d on branch b3');",
"select dolt_tag('t3')",
"select dolt_checkout('main');",
},
Assertions: []ScriptTestAssertion{
{
Query: "select active_branch();",
Expected: []sql.Row{{"main"}},
},
{
Query: "select column_name from information_schema.columns where table_catalog = 'postgres' and table_name = 't' order by 1;",
Expected: []sql.Row{{"a"}, {"b"}},
},
{
Query: "use postgres/t2;",
SkipResultsCheck: true,
},
{
Query: "select active_branch();",
Expected: []sql.Row{{nil}},
},
{
Query: "select column_name from information_schema.columns where table_catalog = 'postgres/t2' and table_name = 't' order by 1;",
Expected: []sql.Row{{"a"}, {"b"}, {"c"}},
},
{
Query: "use postgres/t3;",
SkipResultsCheck: true,
},
{
Query: "select active_branch();",
Expected: []sql.Row{{nil}},
},
{
Query: "select column_name from information_schema.columns where table_catalog = 'postgres/t3' and table_name = 't' order by 1;",
Expected: []sql.Row{{"a"}, {"b"}, {"d"}},
},
{
Query: "select relname from pg_class where oid = 't'::regclass;",
Expected: []sql.Row{{"t"}},
},
},
},
}
func TestInfoSchemaColumns(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "information_schema.columns",
SetUpScript: []string{
"create table test_table (id int primary key, col1 varchar(255));",
"create view test_view as select * from test_table;",
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT DISTINCT table_schema FROM information_schema.columns ORDER BY table_schema;`,
Expected: []sql.Row{
{"information_schema"}, {"pg_catalog"}, {"public"},
},
},
{
Query: `SELECT table_catalog, table_schema, table_name, column_name FROM information_schema.columns WHERE table_schema='public' ORDER BY table_name;`,
Expected: []sql.Row{
{"postgres", "public", "test_table", "id"},
{"postgres", "public", "test_table", "col1"},
{"postgres", "public", "test_view", ""},
},
},
{
Query: `SELECT
columns.column_name,
pg_catalog.col_description(('"' || table_catalog || '"."' || table_schema || '"."' || table_name || '"')::regclass::oid, ordinal_position) AS description,
('"' || "udt_schema" || '"."' || "udt_name" || '"')::"regtype" AS "regtype",
pg_catalog.format_type("col_attr"."atttypid", "col_attr"."atttypmod") AS "format_type"
FROM "information_schema"."columns"
LEFT JOIN "pg_catalog"."pg_attribute" AS "col_attr"
ON "col_attr"."attname" = "columns"."column_name" AND "col_attr"."attrelid" = (
SELECT "cls"."oid" FROM "pg_catalog"."pg_class" AS "cls"
LEFT JOIN "pg_catalog"."pg_namespace" AS "ns" ON "ns"."oid" = "cls"."relnamespace"
WHERE "cls"."relname" = "columns"."table_name" AND "ns"."nspname" = "columns"."table_schema"
) WHERE ("table_schema" = 'public' AND "table_name" = 'test_table');`,
Expected: []sql.Row{
{"id", nil, "integer", "integer"},
{"col1", nil, "character varying", "character varying"},
},
},
{
Query: `CREATE SCHEMA test_schema;`,
Expected: []sql.Row{},
},
{
Query: `SET SEARCH_PATH TO test_schema;`,
Expected: []sql.Row{},
},
{
Query: `CREATE TABLE test_table2 (id2 INT);`,
Expected: []sql.Row{},
},
{
Query: `SELECT DISTINCT table_schema FROM information_schema.columns order by table_schema;`,
Expected: []sql.Row{
{"information_schema"}, {"pg_catalog"}, {"public"}, {"test_schema"},
},
},
{
Query: `SELECT table_catalog, table_schema, table_name, column_name FROM information_schema.columns WHERE table_schema='test_schema';`,
Expected: []sql.Row{
{"postgres", "test_schema", "test_table2", "id2"},
},
},
{
Query: "SELECT * FROM information_schema.columns WHERE table_name='test_table';",
Expected: []sql.Row{
{"postgres", "public", "test_table", "id", 1, nil, "NO", "integer", nil, nil, 32, 2, 0, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "postgres", "pg_catalog", "int4", nil, nil, nil, nil, nil, "NO", "NO", nil, nil, nil, nil, nil, "NO", "NEVER", nil, "YES"},
{"postgres", "public", "test_table", "col1", 2, nil, "YES", "character varying", 255, 1020, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "postgres", "pg_catalog", "varchar", nil, nil, nil, nil, nil, "NO", "NO", nil, nil, nil, nil, nil, "NO", "NEVER", nil, "YES"},
},
},
{
Skip: true, // TODO: Don't have complete view information to fill out these rows
Query: "SELECT * FROM information_schema.columns WHERE table_name='test_view';",
Expected: []sql.Row{
{"postgres", "public", "test_view", "id", 1, nil, "YES", "integer", nil, nil, 32, 2, 0, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "postgres", "pg_catalog", "int4", nil, nil, nil, nil, 1, "NO", "NO", nil, nil, nil, nil, nil, "NO", "NEVER", nil, "YES"},
{"postgres", "public", "test_view", "col1", 2, nil, "YES", "character varying", 255, 1020, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "postgres", "pg_catalog", "varchar", nil, nil, nil, nil, 2, "NO", "NO", nil, nil, nil, nil, nil, "NO", "NEVER", nil, "YES"},
},
},
{
Query: `SELECT columns.table_name, columns.column_name from "information_schema"."columns" WHERE table_name='test_table';`,
Expected: []sql.Row{
{"test_table", "id"},
{"test_table", "col1"},
},
},
{
Query: `CREATE TABLE testnumtypes (id INT PRIMARY KEY, col1 SMALLINT, col2 BIGINT, col3 REAL, col4 DOUBLE PRECISION, col5 NUMERIC, col6 DECIMAL(10, 2), col7 OID, col8 XID);`,
Expected: []sql.Row{},
},
{
Query: "SELECT column_name, ordinal_position, data_type, udt_name, numeric_precision, numeric_precision_radix, numeric_scale FROM information_schema.columns WHERE table_name='testnumtypes' ORDER BY ordinal_position ASC;",
Expected: []sql.Row{
{"id", 1, "integer", "int4", 32, 2, 0},
{"col1", 2, "smallint", "int2", 16, 2, 0},
{"col2", 3, "bigint", "int8", 64, 2, 0},
{"col3", 4, "real", "float4", 24, 2, nil},
{"col4", 5, "double precision", "float8", 53, 2, nil},
{"col5", 6, "numeric", "numeric", nil, 10, nil},
{"col6", 7, "numeric", "numeric", 10, 10, 2},
{"col7", 8, "oid", "oid", nil, nil, nil},
{"col8", 9, "xid", "xid", nil, nil, nil},
},
},
{
Query: `CREATE TABLE teststringtypes (id INT PRIMARY KEY, col1 CHAR(10), col2 VARCHAR(10), col3 TEXT, col4 "char", col5 CHARACTER, col6 VARCHAR, col7 UUID);`,
Expected: []sql.Row{},
},
{
Query: "SELECT column_name, ordinal_position, data_type, udt_name, character_maximum_length, character_octet_length FROM information_schema.columns WHERE table_name='teststringtypes' ORDER BY ordinal_position ASC;",
Expected: []sql.Row{
{"id", 1, "integer", "int4", nil, nil},
{"col1", 2, "character", "bpchar", 10, 40},
{"col2", 3, "character varying", "varchar", 10, 40},
{"col3", 4, "text", "text", nil, 1073741824},
{"col4", 5, `"char"`, "char", nil, nil},
{"col5", 6, "character", "bpchar", 1, 4},
{"col6", 7, "character varying", "varchar", nil, 1073741824},
{"col7", 8, "uuid", "uuid", nil, nil},
},
},
{
Query: `CREATE TABLE testtimetypes (id INT PRIMARY KEY, col1 DATE, col2 TIME, col3 TIMESTAMP, col4 TIMESTAMPTZ, col5 TIMETZ);`,
Expected: []sql.Row{},
},
{
// TODO: Test timestamps with precision when it is implemented
Query: "SELECT column_name, ordinal_position, data_type, datetime_precision FROM information_schema.columns WHERE table_name='testtimetypes' ORDER BY ordinal_position ASC;",
Expected: []sql.Row{
{"id", 1, "integer", nil},
{"col1", 2, "date", 0},
{"col2", 3, "time without time zone", 6},
{"col3", 4, "timestamp without time zone", 6},
{"col4", 5, "timestamp with time zone", 6},
{"col5", 6, "time with time zone", 6},
},
},
{
Query: `SELECT p.oid AS oid, p.relname AS table_name, n.nspname as table_schema FROM pg_class AS p JOIN pg_namespace AS n ON p.relnamespace=n.oid WHERE (n.nspname='public' AND p.relkind='r') AND left(relname, 5) <> 'dolt_';`,
Expected: []sql.Row{{2957635223, "test_table", "public"}},
},
{
Query: `select col_description(2957635223, ordinal_position) as comment from information_schema.columns limit 1;`,
Expected: []sql.Row{{nil}},
},
},
},
})
}
func TestInfoSchemaSchemata(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "information_schema.schemata",
Database: "newdb",
SetUpScript: []string{
"create schema test_schema",
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT catalog_name, schema_name FROM information_schema.schemata order by schema_name;`,
Expected: []sql.Row{
{"newdb", "dolt"},
{"newdb", "information_schema"},
{"newdb", "pg_catalog"},
{"newdb", "public"},
{"newdb", "test_schema"},
},
},
{
Query: `SELECT * FROM information_schema.schemata order by schema_name;`,
Expected: []sql.Row{
{"newdb", "dolt", "", nil, nil, nil, nil},
{"newdb", "information_schema", "", nil, nil, nil, nil},
{"newdb", "pg_catalog", "", nil, nil, nil, nil},
{"newdb", "public", "", nil, nil, nil, nil},
{"newdb", "test_schema", "", nil, nil, nil, nil},
},
},
},
},
})
}
func TestInfoSchemaTables(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "information_schema.tables",
SetUpScript: []string{
"create table test_table (id int)",
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM information_schema.tables WHERE table_name='test_table';`,
Expected: []sql.Row{
{"postgres", "public", "test_table", "BASE TABLE", nil, nil, nil, nil, nil, "YES", "NO", nil},
},
},
{
Query: `SELECT DISTINCT table_schema FROM information_schema.tables order by table_schema;`,
Expected: []sql.Row{
{"information_schema"}, {"pg_catalog"}, {"public"},
},
},
{
Query: `SELECT table_catalog, table_schema FROM information_schema.tables group by table_catalog, table_schema order by table_schema;`,
Expected: []sql.Row{
{"postgres", "information_schema"},
{"postgres", "pg_catalog"},
{"postgres", "public"},
},
},
{
Query: `SELECT table_catalog, table_schema, table_name FROM information_schema.tables WHERE table_schema='public';`,
Expected: []sql.Row{
{"postgres", "public", "test_table"},
},
},
{
Query: `CREATE SCHEMA test_schema;`,
Expected: []sql.Row{},
},
{
Query: `SET SEARCH_PATH TO test_schema;`,
Expected: []sql.Row{},
},
{
Query: `CREATE TABLE test_table2 (id INT);`,
Expected: []sql.Row{},
},
{
Query: `SELECT DISTINCT table_schema FROM information_schema.tables order by table_schema;`,
Expected: []sql.Row{
{"information_schema"}, {"pg_catalog"}, {"public"}, {"test_schema"},
},
},
{
Query: `SELECT table_catalog, table_schema FROM information_schema.tables group by table_catalog, table_schema order by table_schema;`,
Expected: []sql.Row{
{"postgres", "information_schema"},
{"postgres", "pg_catalog"},
{"postgres", "public"},
{"postgres", "test_schema"},
},
},
{
Query: `SELECT table_catalog, table_schema, table_name FROM information_schema.tables WHERE table_schema='test_schema';`,
Expected: []sql.Row{
{"postgres", "test_schema", "test_table2"},
},
},
{
Query: "SELECT table_catalog, table_schema, table_name, table_type FROM information_schema.tables WHERE table_schema = 'test_schema' ORDER BY table_name;",
Expected: []sql.Row{
{"postgres", "test_schema", "test_table2", "BASE TABLE"},
},
},
{
Query: `SELECT "table_schema", "table_name", obj_description(('"' || "table_schema" || '"."' || "table_name" || '"')::regclass, 'pg_class') AS table_comment FROM "information_schema"."tables" WHERE ("table_schema" = 'test_schema' AND "table_name" = 'test_table2')`,
Expected: []sql.Row{{"test_schema", "test_table2", nil}},
},
{
Query: `CREATE VIEW test_view AS SELECT * FROM test_table2;`,
Expected: []sql.Row{},
},
{
Query: `SELECT table_catalog, table_schema, table_name, table_type FROM information_schema.tables WHERE table_schema='test_schema' OR table_schema='public';`,
Expected: []sql.Row{
{"postgres", "public", "test_table", "BASE TABLE"},
{"postgres", "test_schema", "test_view", "VIEW"},
{"postgres", "test_schema", "test_table2", "BASE TABLE"},
},
},
},
},
})
}
func TestInfoSchemaViews(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "information_schema.views",
SetUpScript: []string{
"create table test_table (id int)",
"create view test_view as select * from test_table",
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM information_schema.views order by table_schema;`,
Expected: []sql.Row{
{"postgres", "public", "test_view", "SELECT * FROM test_table", "NONE", nil, nil, nil, nil, nil},
},
},
{
Query: `SELECT DISTINCT table_schema FROM information_schema.views order by table_schema;`,
Expected: []sql.Row{
{"public"},
},
},
{
Query: `SELECT table_catalog, table_schema FROM information_schema.views group by table_catalog, table_schema order by table_schema;`,
Expected: []sql.Row{
{"postgres", "public"},
},
},
{
Query: `SELECT table_catalog, table_schema, table_name FROM information_schema.views WHERE table_schema='public';`,
Expected: []sql.Row{
{"postgres", "public", "test_view"},
},
},
{
Query: `CREATE SCHEMA test_schema;`,
Expected: []sql.Row{},
},
{
Query: `SET SEARCH_PATH TO test_schema;`,
Expected: []sql.Row{},
},
{
Query: `CREATE TABLE test_table2 (id int);`,
Expected: []sql.Row{},
},
{
Query: `CREATE VIEW test_view2 as select * from test_table2;`,
Expected: []sql.Row{},
},
{
Query: `SELECT DISTINCT table_schema FROM information_schema.views order by table_schema;`,
Expected: []sql.Row{
{"public"},
{"test_schema"},
},
},
{
Query: `SELECT table_catalog, table_schema FROM information_schema.views group by table_catalog, table_schema order by table_schema;`,
Expected: []sql.Row{
{"postgres", "public"},
{"postgres", "test_schema"},
},
},
{
Query: `SELECT table_catalog, table_schema, table_name FROM information_schema.views WHERE table_schema='test_schema';`,
Expected: []sql.Row{
{"postgres", "test_schema", "test_view2"},
},
},
{
Query: "SELECT table_catalog, table_schema, table_name, view_definition FROM information_schema.views WHERE table_schema = 'test_schema' ORDER BY table_name;",
Expected: []sql.Row{
{"postgres", "test_schema", "test_view2", "SELECT * FROM test_table2"},
},
},
},
},
})
}
func TestInfoSchemaSequences(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "information_schema.sequences",
SetUpScript: []string{
"create sequence standard as smallint;",
"create schema test_schema",
"create table test_schema.test_table (id serial);",
"create sequence big increment by 3 start with 10 minvalue 1 cycle;",
"create sequence negative increment by -1",
},
Assertions: []ScriptTestAssertion{
{
Query: "select * from information_schema.sequences where sequence_name = 'standard';",
Expected: []sql.Row{
{"postgres", "public", "standard", "smallint", 16, 2, 0, "1", "1", "32767", "1", "NO"},
},
},
{
Query: "select sequence_schema,sequence_name,data_type,numeric_precision from information_schema.sequences where sequence_name = 'test_table_id_seq';",
Expected: []sql.Row{
{"test_schema", "test_table_id_seq", "integer", 32},
},
},
{
Query: "select sequence_name,data_type,numeric_precision,minimum_value,increment,cycle_option from information_schema.sequences where sequence_name = 'big';",
Expected: []sql.Row{
{"big", "bigint", 64, "1", "3", "YES"},
},
},
{
Query: "select sequence_name, increment from information_schema.sequences where sequence_name = 'negative';",
Expected: []sql.Row{
{"negative", "-1"},
},
},
},
},
})
}
+297
View File
@@ -0,0 +1,297 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestInsert(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "simple insert",
SetUpScript: []string{
"CREATE TABLE mytable (id INT PRIMARY KEY, name TEXT)",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO mytable (id, name) VALUES (1, 'hello')",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO mytable (ID, naME) VALUES (2, 'world')",
SkipResultsCheck: true,
},
{
Query: "SELECT * FROM mytable order by id",
Expected: []sql.Row{
{1, "hello"},
{2, "world"},
},
},
},
},
{
Name: "keyless insert",
SetUpScript: []string{
"CREATE TABLE mytable (id INT, name TEXT)",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO mytable (id, name) VALUES (1, 'hello')",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO mytable (ID, naME) VALUES (2, 'world')",
SkipResultsCheck: true,
},
{
Query: "SELECT * FROM mytable order by id",
Expected: []sql.Row{
{1, "hello"},
{2, "world"},
},
},
},
},
{
Name: "on conflict clause",
SetUpScript: []string{
"CREATE TABLE mytable (id INT primary key, name TEXT)",
"create table t2 (id int primary key, c1 text, c2 text)",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO mytable (id, name) VALUES (1, 'hello')",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO mytable (ID, naME) VALUES (2, 'world')",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO mytable (ID, naME) VALUES (1, 'world') ON CONFLICT (id) DO UPDATE SET name = 'world'",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO mytable (ID, naME) VALUES (2, 'hello') ON CONFLICT (id) DO UPDATE SET name = 'conflict'",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO mytable (ID, naME) VALUES (1, 'not inserted') ON CONFLICT (id) DO NOTHING",
},
{
Query: "SELECT * FROM mytable order by id",
Expected: []sql.Row{
{1, "world"},
{2, "conflict"},
},
},
{
Query: "INSERT INTO mytable (ID, naME) VALUES (1, 'hello') ON CONFLICT (id) DO UPDATE set name = concat('new', name)",
},
{
Query: "SELECT * FROM mytable order by id",
Expected: []sql.Row{
{1, "newworld"},
{2, "conflict"},
},
},
{
Query: "INSERT INTO t2 (id, c1, c2) VALUES (1, 'hello', 'world'), (2, 'world', 'hello')",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO t2 (id, c1, c2) VALUES (1, 'hello', 'world') ON CONFLICT (id) DO UPDATE SET c1 = 'conflict', c2 = c1",
SkipResultsCheck: true,
},
{
Query: "INSERT INTO t2 (id, c1, c2) VALUES (2, 'hello', 'world') ON CONFLICT (id) DO UPDATE SET c2 = c1",
SkipResultsCheck: true,
},
{
Query: "SELECT * FROM t2 order by id",
Expected: []sql.Row{
{1, "conflict", "conflict"},
{2, "world", "world"},
},
},
{
Query: `INSERT INTO t2 (id, c1, c2)
VALUES ($1, $2, $3)
ON CONFLICT (id) do update set c1 = $4`,
BindVars: []any{1, "x", "y", "no conflict expected"},
},
},
},
{
Name: "null and unspecified default values",
SetUpScript: []string{
"CREATE TABLE t (i INT DEFAULT NULL, j INT)",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t VALUES (default, default)",
SkipResultsCheck: true,
},
{
Query: "SELECT * FROM t",
Expected: []sql.Row{
{nil, nil},
},
},
},
},
{
Name: "implicit default values",
SetUpScript: []string{
"CREATE TABLE t (i INT DEFAULT 123, j INT default 456);",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t DEFAULT VALUES;",
SkipResultsCheck: true,
},
{
Query: "SELECT * FROM t",
Expected: []sql.Row{
{123, 456},
},
},
},
},
{
Name: "types",
SetUpScript: []string{
`create table child (i2 int2, i4 int4, i8 int8, f float, d double precision, v varchar, vl varchar(100), t text, j json, ts timestamp);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `insert into child values (1, 2, 3, 4.5, 6.7, 'hello', 'world', 'text', '{"a": 1}', '2021-01-01 00:00:00');`,
},
{
Query: `select * from child;`,
Expected: []sql.Row{
{int16(1), int32(2), int64(3), float32(4.5), float64(6.7), "hello", "world", "text", `{"a": 1}`, "2021-01-01 00:00:00"},
},
},
},
},
{
Name: "insert returning",
SetUpScript: []string{
"CREATE TABLE t (i serial, j INT)",
"CREATE TABLE u (u uuid DEFAULT 'ac1f3e2d-1e4b-4d3e-8b1f-2b7f1e7f0e3d', j INT)",
"CREATE TABLE s (v1 varchar DEFAULT 'hello', v2 varchar DEFAULT 'world')",
"CREATE SCHEMA ts",
"CREATE TABLE ts.t (i serial, j INT)",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO t (j) VALUES (5), (6), (7) RETURNING i",
Expected: []sql.Row{
{1}, {2}, {3},
},
},
{
Query: "INSERT INTO t (j) VALUES (5), (6), (7) RETURNING i+3",
Expected: []sql.Row{
{7}, {8}, {9},
},
},
{
Query: "INSERT INTO t (j) VALUES (5), (6), (7) RETURNING i+j, j-3*i",
Expected: []sql.Row{
{12, -16}, {14, -18}, {16, -20},
},
},
{
Query: "INSERT INTO u (j) VALUES (5), (6), (7) RETURNING u",
Expected: []sql.Row{
{"ac1f3e2d-1e4b-4d3e-8b1f-2b7f1e7f0e3d"}, {"ac1f3e2d-1e4b-4d3e-8b1f-2b7f1e7f0e3d"}, {"ac1f3e2d-1e4b-4d3e-8b1f-2b7f1e7f0e3d"},
},
},
{
Query: "INSERT INTO s (v2) VALUES (' a') RETURNING concat(v1, v2)",
Expected: []sql.Row{
{"hello a"},
},
},
{
Query: "INSERT INTO s (v1) VALUES ('sup ') RETURNING concat(v1, v2)",
Expected: []sql.Row{
{"sup world"},
},
},
{
Query: "INSERT INTO s (v2, v1) VALUES ('def', 'abc'), ('xyz', 'uvw') RETURNING concat(v1, v2), concat(v2, v1), 100",
Expected: []sql.Row{
{"abcdef", "defabc", 100},
{"uvwxyz", "xyzuvw", 100},
},
},
{
Query: "INSERT INTO t (j) VALUES (5), (6), (7) RETURNING i, doesnotexist",
ExpectedErr: "could not be found",
},
{
Query: "INSERT INTO t (j) VALUES (5), (6), (7) RETURNING i, doesnotexist(j)",
ExpectedErr: "function: 'doesnotexist' not found",
},
{
Query: "INSERT INTO public.t (j) VALUES (8) RETURNING t.j",
Expected: []sql.Row{{8}},
},
{
Query: "INSERT INTO public.t (j) VALUES (9) RETURNING public.t.j",
Expected: []sql.Row{{9}},
},
{
Query: "INSERT INTO ts.t (j) VALUES (10) RETURNING ts.t.j",
Expected: []sql.Row{{10}},
},
{
Query: "INSERT INTO public.t (j) VALUES ($1) RETURNING j;",
BindVars: []any{11},
Expected: []sql.Row{{11}},
},
{
Query: "INSERT INTO public.t (j) VALUES ($1) RETURNING t.j;",
BindVars: []any{12},
Expected: []sql.Row{{12}},
},
{
Query: "INSERT INTO public.t (j) VALUES ($1) RETURNING public.t.j;",
BindVars: []any{13},
Expected: []sql.Row{{13}},
},
},
},
{
Name: "insert iso8601 timestamptz literal",
SetUpScript: []string{
"CREATE TABLE django_migrations (id serial primary key, app varchar, name varchar, applied timestamptz)",
},
Assertions: []ScriptTestAssertion{
{
Query: `INSERT INTO "django_migrations" ("app", "name", "applied") VALUES ('contenttypes', '0001_initial', '2025-03-24T19:21:59.690479+00:00'::timestamptz) RETURNING "django_migrations"."id"`,
Expected: []sql.Row{{1}},
},
},
},
})
}
+651
View File
@@ -0,0 +1,651 @@
// Copyright 2023 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
"github.com/jackc/pgx/v5/pgproto3"
"github.com/dolthub/doltgresql/core/id"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
func TestIssues(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Issue #25",
SetUpScript: []string{
"create table tbl (pk int);",
"insert into tbl values (1);",
},
Assertions: []ScriptTestAssertion{
{
Query: `select dolt_add(".");`,
ExpectedErr: "could not be found in any table in scope",
},
{
Query: `select dolt_add('.');`,
Expected: []sql.Row{{"{0}"}},
},
{
Query: `select dolt_commit("-m", "look ma");`,
ExpectedErr: "could not be found in any table in scope",
},
{
Query: `select length(dolt_commit('-m', 'look ma')::text);`,
Expected: []sql.Row{{34}},
},
{
Query: `select dolt_branch("br1");`,
ExpectedErr: "could not be found in any table in scope",
},
{
Query: `select dolt_branch('br1');`,
Expected: []sql.Row{{"{0}"}},
},
},
},
{
Name: "Issue #2030",
SetUpScript: []string{
`CREATE TABLE sub_entities (
project_id VARCHAR(256) NOT NULL,
entity_id VARCHAR(256) NOT NULL,
id VARCHAR(256) NOT NULL,
name VARCHAR(256) NOT NULL,
PRIMARY KEY (project_id, entity_id, id)
);
`,
`
CREATE TABLE entities (
project_id VARCHAR(256) NOT NULL,
id VARCHAR(256) NOT NULL,
name VARCHAR(256) NOT NULL,
default_sub_entity_id VARCHAR(256),
PRIMARY KEY (project_id, id)
);
`,
`
CREATE TABLE conversations (
id VARCHAR(256) NOT NULL,
tenant_id VARCHAR(256) NOT NULL,
project_id VARCHAR(256) NOT NULL,
active_sub_agent_id VARCHAR(256) NOT NULL,
PRIMARY KEY (tenant_id, project_id, id)
);
`,
`INSERT INTO sub_entities (project_id, entity_id, id, name) VALUES
('projectA', 'entityA', 'subA1', 'Sub-Entity A1'),
('projectA', 'entityB', 'subB1', 'Sub-Entity B1');
`,
`INSERT INTO entities (project_id, id, name, default_sub_entity_id) VALUES
('projectA', 'entityA', 'Entity A', 'subA1'),
('projectA', 'entityB', 'Entity B', 'subB1');
`,
`INSERT INTO conversations (tenant_id, project_id, id, active_sub_agent_id) VALUES
('tenant1', 'projectA', 'conv1', 'subA1'),
('tenant1', 'projectA', 'conv2', 'subB1');
`,
},
Assertions: []ScriptTestAssertion{
{
Query: `select
"entities"."project_id",
"entities"."id",
"entities"."name",
"entities"."default_sub_entity_id",
"entities_defaultSubEntity"."data" as "defaultSubEntity"
from "entities" "entities"
left join lateral (
select json_build_array(
"entities_defaultSubEntity"."project_id",
"entities_defaultSubEntity"."entity_id",
"entities_defaultSubEntity"."id",
"entities_defaultSubEntity"."name"
) as "data"
from (
select * from "sub_entities" "entities_defaultSubEntity"
where "entities_defaultSubEntity"."id" = "entities"."default_sub_entity_id"
limit $1
) "entities_defaultSubEntity"
) "entities_defaultSubEntity" on true
where ("entities"."project_id" = $2 and "entities"."id" = $3)
limit $4`,
BindVars: []any{
int64(1),
"projectA",
"entityA",
int64(1),
},
Expected: []sql.Row{
{
"projectA",
"entityA",
"Entity A",
"subA1",
`["projectA", "entityA", "subA1", "Sub-Entity A1"]`,
},
},
},
{
Query: `select
"entities"."project_id",
"entities"."id",
"entities"."name",
"entities"."default_sub_entity_id",
"entities_defaultSubEntity"."data" as "defaultSubEntity"
from "entities" "entities"
left join lateral (
select json_build_array(
"entities_defaultSubEntity"."project_id",
"entities_defaultSubEntity"."entity_id",
"entities_defaultSubEntity"."id",
"entities_defaultSubEntity"."name"
) as "data"
from (
select * from "sub_entities" "entities_defaultSubEntity"
where "entities_defaultSubEntity"."id" = "entities"."default_sub_entity_id"
limit 1
) "entities_defaultSubEntity"
) "entities_defaultSubEntity" on true
where ("entities"."project_id" = 'projectA' and "entities"."id" = 'entityA')
limit 1`,
Expected: []sql.Row{
{
"projectA",
"entityA",
"Entity A",
"subA1",
`["projectA", "entityA", "subA1", "Sub-Entity A1"]`,
},
},
},
},
},
{
Name: "Issue #2049",
SetUpScript: []string{
`CREATE TABLE jsonb_test (id VARCHAR(256) NOT NULL PRIMARY KEY, "jsonbColumn" JSONB);`,
`INSERT INTO jsonb_test VALUES ('test', '{"test": "value\n"}');`,
`INSERT INTO jsonb_test VALUES ('test2', '{"test": "value\t"}');`,
`INSERT INTO jsonb_test VALUES ('test3', '{"test": "value\r"}');`,
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT * FROM jsonb_test;",
// The pgx library incorrectly reinterprets our JSON value by replacing the individual newline
// characters (ASCII 92,110) with the actual newline character (ASCII 10), which is incorrect for us.
// Therefore, we have to use the raw returned values. To make it more clear, we aren't using a raw
// string literal and instead escaping the characters in the byte slice. We also test other escape
// characters that are replaced.
ExpectedRaw: [][][]byte{
{[]byte("test"), []byte("{\"test\": \"value\\n\"}")},
{[]byte("test2"), []byte("{\"test\": \"value\\t\"}")},
{[]byte("test3"), []byte("{\"test\": \"value\\r\"}")},
},
},
},
},
{
Name: "Issue #2197 Part 1",
SetUpScript: []string{
`CREATE TABLE t1 (a INT, b VARCHAR(3));`,
`CREATE TABLE t2(id SERIAL, t1 t1);`,
},
Assertions: []ScriptTestAssertion{
{
Query: `INSERT INTO t2(t1) VALUES (ROW(1, 'abc'));`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM t2;`,
Expected: []sql.Row{{1, "(1,abc)"}},
},
{
Query: `INSERT INTO t2(t1) VALUES (ROW('a', 'def'));`,
ExpectedErr: "invalid input syntax for type",
},
{
Query: `INSERT INTO t2(t1) VALUES (ROW(true, 'def'));`,
ExpectedErr: "Cannot cast type",
},
{
Query: `INSERT INTO t2(t1) VALUES (ROW(2, 'def', 'ghi'));`,
ExpectedErr: "cannot cast type",
},
{
Query: `INSERT INTO t2(t1) VALUES (ROW(2));`,
ExpectedErr: "cannot cast type",
},
},
},
{
Name: "Issue #2197 Part 2",
SetUpScript: []string{
`CREATE TABLE t1a (a INT4, b VARCHAR(3));`,
`CREATE TABLE t1b (a INT4 NOT NULL, b VARCHAR(3) NOT NULL);`,
`CREATE TABLE t2 (id SERIAL, t1a t1a, t1b t1b);`,
`INSERT INTO t2 (t1a) VALUES (ROW(1, 'abc'));`,
`INSERT INTO t2 (t1b) VALUES (ROW(1, 'abc'));`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM t2;`,
Expected: []sql.Row{
{1, "(1,abc)", nil},
{2, nil, "(1,abc)"},
},
},
{
Query: `ALTER TABLE t1a ADD COLUMN c VARCHAR(10);`,
Expected: []sql.Row{},
},
{
Query: `ALTER TABLE t1b ADD COLUMN c VARCHAR(10) NOT NULL;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM t2 ORDER BY id;`,
Expected: []sql.Row{
{1, "(1,abc,)", nil},
{2, nil, "(1,abc,)"},
},
},
{
Query: `ALTER TABLE t1a DROP COLUMN b;`,
Expected: []sql.Row{},
},
{
Query: `ALTER TABLE t1b DROP COLUMN b;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM t2 ORDER BY id;`,
Expected: []sql.Row{
{1, "(1,)", nil},
{2, nil, "(1,)"},
},
},
{
Query: `INSERT INTO t1a VALUES (2, 'def');`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO t1b VALUES (3, 'xyzzy');`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO t2 (t1a) SELECT ROW(a,c)::t1a FROM t1a;`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO t2 (t1b) SELECT ROW(a,c)::t1b FROM t1b;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM t2 ORDER BY id;`,
Expected: []sql.Row{
{1, "(1,)", nil},
{2, nil, "(1,)"},
{3, "(2,def)", nil},
{4, nil, "(3,xyzzy)"},
},
},
{
Query: `SELECT ((t1a).@1), ((t1b).@2) FROM t2 ORDER BY id;`,
Expected: []sql.Row{
{1, nil},
{nil, nil},
{2, nil},
{nil, "xyzzy"},
},
},
{
Query: `UPDATE t2 SET t1a=ROW((t1a).a+100, (t1a).c)::t1a WHERE length(t1a::text) > 0;`,
Expected: []sql.Row{},
},
{
Query: `UPDATE t2 SET t1b=ROW((t1b).@1+100, (t1b).@2)::t1b WHERE length(t1b::text) > 0;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM t2 ORDER BY id;`,
Expected: []sql.Row{
{1, "(101,)", nil},
{2, nil, "(101,)"},
{3, "(102,def)", nil},
{4, nil, "(103,xyzzy)"},
},
},
{
Query: `SELECT (id).a FROM t2;`,
ExpectedErr: "column notation .a applied to type",
},
{
Query: `SELECT (t1a).g FROM t2;`,
ExpectedErr: `column "g" not found in data type`,
},
{
Query: `SELECT (t1a).@0 FROM t2;`,
ExpectedErr: "out of bounds",
},
{
Query: `SELECT (t1a).@3 FROM t2;`,
ExpectedErr: "out of bounds",
},
{
Query: `ALTER TABLE t1a ADD COLUMN d VARCHAR(10) DEFAULT 'abc';`,
ExpectedErr: `cannot alter table "t1a" because column "t2.t1a" uses its row type`,
},
{
Query: `ALTER TABLE t1a ADD COLUMN d VARCHAR(10);`,
Expected: []sql.Row{},
},
{
Query: `ALTER TABLE t1a DROP COLUMN c;`,
Expected: []sql.Row{},
},
{
Query: `SELECT * FROM t2 ORDER BY id;`,
Expected: []sql.Row{
{1, "(101,)", nil},
{2, nil, "(101,)"},
{3, "(102,)", nil},
{4, nil, "(103,xyzzy)"},
},
},
},
},
{
Name: "Issue #2299",
SetUpScript: []string{
"CREATE TYPE team_role AS ENUM ('admin', 'editor', 'member');",
},
Assertions: []ScriptTestAssertion{
{
Query: `CREATE TABLE users (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), role team_role NOT NULL DEFAULT 'member');`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO users (role) VALUES (DEFAULT);`,
Expected: []sql.Row{},
},
{
Query: `SELECT role FROM users;`,
Expected: []sql.Row{{"member"}},
},
},
},
{
Name: "Issue #2307",
SetUpScript: []string{
"CREATE TABLE test (pk INT4);",
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT EXISTS(SELECT 1 FROM pg_catalog.pg_tables WHERE tablename = 'test');`,
ExpectedColTypes: []id.Type{pgtypes.Bool.ID},
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT NOT EXISTS(SELECT 1 FROM pg_catalog.pg_tables WHERE tablename = 'test');`,
ExpectedColTypes: []id.Type{pgtypes.Bool.ID},
Expected: []sql.Row{{"f"}},
},
},
},
{
Name: "Issue #2548",
SetUpScript: []string{
"CREATE TABLE test (pk INT4 PRIMARY KEY, v1 TIMESTAMP WITH TIME ZONE);",
},
Assertions: []ScriptTestAssertion{
{
Query: `SET TimeZone = 'UTC-01:00';`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO test VALUES (1, '2026-04-15 10:11:12');`,
Expected: []sql.Row{},
},
{
Query: `SET TimeZone = 'UTC-03:00';`,
Expected: []sql.Row{},
},
{
Query: `INSERT INTO test VALUES (2, '2026-04-15 10:11:12');`,
Expected: []sql.Row{},
},
{
Query: `SELECT (SELECT v1 FROM test WHERE pk = 2) - (SELECT v1 FROM test WHERE pk = 1);`,
Expected: []sql.Row{{"-02:00:00"}},
},
},
},
{
Name: "Issue #2604",
SetUpScript: []string{
"CREATE TABLE t (id INT PRIMARY KEY, a TEXT, b TEXT DEFAULT 'x');",
"CREATE UNIQUE INDEX idx_t_a ON t(a);",
"SELECT dolt_add('-A');",
"SELECT dolt_commit('-m', 'schema');",
"SELECT dolt_branch('f', 'main');",
"SELECT dolt_checkout('f');",
"INSERT INTO t (id, a) VALUES (1, 'feat');",
"SELECT dolt_add('-A');",
"SELECT dolt_commit('-m', 'feat');",
"SELECT dolt_checkout('main');",
"INSERT INTO t (id, a) VALUES (2, 'main');",
"SELECT dolt_add('-A');",
"SELECT dolt_commit('-m', 'main');",
"SELECT dolt_checkout('f');",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT length(dolt_merge('main')::text) = 57;",
Expected: []sql.Row{{"t"}},
},
},
},
})
}
func TestIssuesWire(t *testing.T) {
RunWireScripts(t, []WireScriptTest{
{
Name: "Issue #2546",
Assertions: []WireScriptTestAssertion{
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Query{String: "SELECT 'foo';"},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.RowDescription{
Fields: []pgproto3.FieldDescription{
{
Name: []byte("?column?"),
TableOID: 0,
TableAttributeNumber: 0,
DataTypeOID: 25,
DataTypeSize: -1,
TypeModifier: -1,
Format: 0,
},
},
},
&pgproto3.DataRow{Values: [][]byte{[]byte("foo")}},
&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
},
},
{
Name: "Issue #2557",
Assertions: []WireScriptTestAssertion{
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Parse{
Name: "stmt_name1",
Query: `SELECT '{"v":"a\\nb"}'::jsonb;`,
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.ParseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Bind{
PreparedStatement: "stmt_name1",
ResultFormatCodes: []int16{0},
},
&pgproto3.Execute{},
&pgproto3.Close{
ObjectType: 'P',
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.BindComplete{},
&pgproto3.DataRow{
Values: [][]byte{
[]byte(`{"v": "a\\nb"}`),
},
},
&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")},
&pgproto3.CloseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Parse{
Name: "stmt_name2",
Query: `SELECT $${"v":"a\\nb"}$$::jsonb;`,
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.ParseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Bind{
PreparedStatement: "stmt_name2",
ResultFormatCodes: []int16{0},
},
&pgproto3.Execute{},
&pgproto3.Close{
ObjectType: 'P',
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.BindComplete{},
&pgproto3.DataRow{
Values: [][]byte{
[]byte(`{"v": "a\\nb"}`),
},
},
&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")},
&pgproto3.CloseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Parse{
Name: "stmt_name3",
Query: `SELECT $${"v":"a\\\nb"}$$::jsonb;`,
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.ParseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Bind{
PreparedStatement: "stmt_name3",
ResultFormatCodes: []int16{0},
},
&pgproto3.Execute{},
&pgproto3.Close{
ObjectType: 'P',
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.BindComplete{},
&pgproto3.DataRow{
Values: [][]byte{
[]byte(`{"v": "a\\\nb"}`),
},
},
&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")},
&pgproto3.CloseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Parse{
Name: "stmt_name4",
Query: `select json '{ "a": "dollar \\u0024 character" }' ->> 'a' as not_an_escape;`,
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.ParseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
{
Send: []pgproto3.FrontendMessage{
&pgproto3.Bind{
PreparedStatement: "stmt_name4",
ResultFormatCodes: []int16{0},
},
&pgproto3.Execute{},
&pgproto3.Close{
ObjectType: 'P',
},
&pgproto3.Sync{},
},
Receive: []pgproto3.BackendMessage{
&pgproto3.BindComplete{},
&pgproto3.DataRow{
Values: [][]byte{
[]byte(`dollar \u0024 character`),
},
},
&pgproto3.CommandComplete{CommandTag: []byte("SELECT 1")},
&pgproto3.CloseComplete{},
&pgproto3.ReadyForQuery{TxStatus: 'I'},
},
},
},
},
})
}
+963
View File
@@ -0,0 +1,963 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"fmt"
"strings"
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
// makeLargeJSONObject builds a JSONB object literal with the given number of
// keys named k_0000…k_NNNN, each mapped to a small nested object. With 100
// keys the serialized form is roughly 8 KB, which is comfortably above the
// 4 KB threshold that triggers out-of-band storage in Dolt's indexed JSON
// document representation.
func makeLargeJSONObject(numKeys int) string {
var b strings.Builder
b.WriteByte('{')
for i := 0; i < numKeys; i++ {
if i > 0 {
b.WriteByte(',')
}
fmt.Fprintf(&b,
`"k_%04d":{"name":"value_%04d","tags":["tag-a","tag-b","tag-c","tag-d","tag-e"],"n":%d}`,
i, i, i)
}
b.WriteByte('}')
return b.String()
}
// makeLargeJSONArray builds a JSONB array literal with the given number of
// element objects, each labeled row_0000…row_NNNN. With 80 elements the
// serialized form is roughly 5 KB.
func makeLargeJSONArray(numElems int) string {
var b strings.Builder
b.WriteByte('[')
for i := 0; i < numElems; i++ {
if i > 0 {
b.WriteByte(',')
}
fmt.Fprintf(&b,
`{"id":%d,"label":"row_%04d","payload":["a","b","c","d","e"]}`,
i, i)
}
b.WriteByte(']')
return b.String()
}
// makeLargeJSONObjectWithNumericKeys builds a JSONB object large enough to be
// stored as an indexed document, whose "nums" key maps to a nested object with
// numeric string keys. It exercises the extract-path fast path's fallback: a
// numeric path element is first guessed as an array index, which an object
// rejects, so resolution must fall back to treating it as an object key.
func makeLargeJSONObjectWithNumericKeys() string {
padding := makeLargeJSONObject(100)
// Splice a numeric-keyed sub-object onto the front of the padding object,
// dropping the padding's leading '{'.
return `{"nums":{"0":"zero","1":"one","2":"two"},` + padding[1:]
}
// TestJsonObjectField exercises the `->` operator with a text right-hand side
// against both jsonb and json values (jsonb_object_field / json_object_field),
// plus the `->>` text-returning variants. The optimization path uses
// types.LookupJSONValue against SearchableJSON wrappers, but the semantics
// must match for non-object inputs and special keys as well.
func TestJsonObjectField(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "jsonb_object_field returns object value",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":1,"b":"two"}'::jsonb -> 'a';`,
Expected: []sql.Row{{`1`}},
},
{
Query: `SELECT '{"a":1,"b":"two"}'::jsonb -> 'b';`,
Expected: []sql.Row{{`"two"`}},
},
{
Query: `SELECT '{"a":1,"b":"two"}'::jsonb -> null;`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT '{"nested":{"x":[1,2,3]}}'::jsonb -> 'nested';`,
Expected: []sql.Row{{`{"x": [1, 2, 3]}`}},
},
{
// Missing key returns SQL NULL.
Query: `SELECT '{"a":1}'::jsonb -> 'missing';`,
Expected: []sql.Row{{nil}},
},
{
// `->` with a text key on an array returns SQL NULL.
Query: `SELECT '[1,2,3]'::jsonb -> 'a';`,
Expected: []sql.Row{{nil}},
},
{
// `->` with a text key on a scalar returns SQL NULL.
Query: `SELECT '42'::jsonb -> 'a';`,
Expected: []sql.Row{{nil}},
},
{
// Key with a literal dot in it: the optimized lookup
// builds a quoted-key MySQL JSON path, so the dot must
// not be treated as a path separator.
Query: `SELECT '{"a.b":1, "a":{"b":2}}'::jsonb -> 'a.b';`,
Expected: []sql.Row{{`1`}},
},
{
// Key containing a literal double-quote, which must be
// escaped in the constructed MySQL JSON path.
Query: `SELECT '{"a\"b":7}'::jsonb -> 'a"b';`,
Expected: []sql.Row{{`7`}},
},
},
},
{
Name: "json_object_field returns object value",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":1,"b":"two"}'::json -> 'a';`,
Expected: []sql.Row{{`1`}},
},
{
Query: `SELECT '{"a":1,"b":"two"}'::json -> 'b';`,
Expected: []sql.Row{{`"two"`}},
},
{
Query: `SELECT '{"a":1}'::json -> 'missing';`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT '[1,2,3]'::json -> 'a';`,
Expected: []sql.Row{{nil}},
},
},
},
{
Name: "jsonb_object_field_text returns object value as text",
Assertions: []ScriptTestAssertion{
{
// `->>` on a string value returns the raw string (no
// surrounding quotes).
Query: `SELECT '{"a":1,"b":"two"}'::jsonb ->> 'b';`,
Expected: []sql.Row{{`two`}},
},
{
// Numeric value is rendered as its JSON text.
Query: `SELECT '{"a":42}'::jsonb ->> 'a';`,
Expected: []sql.Row{{`42`}},
},
{
// Nested object is rendered as the JSON object text.
Query: `SELECT '{"a":{"b":1}}'::jsonb ->> 'a';`,
Expected: []sql.Row{{`{"b": 1}`}},
},
{
Query: `SELECT '{"a":1}'::jsonb ->> 'missing';`,
Expected: []sql.Row{{nil}},
},
},
},
})
}
// TestJsonArrayElement exercises the `->` operator with an integer right-hand
// side (jsonb_array_element / json_array_element) and the `->>` text variant.
// The optimized path uses $[N] lookups; negative indices fall back to a
// materialized walk to resolve the absolute index.
func TestJsonArrayElement(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "jsonb_array_element returns array element",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '[10,20,30]'::jsonb -> 0;`,
Expected: []sql.Row{{`10`}},
},
{
Query: `SELECT '[10,20,30]'::jsonb -> 2;`,
Expected: []sql.Row{{`30`}},
},
{
// Out-of-range positive index returns SQL NULL.
Query: `SELECT '[10,20,30]'::jsonb -> 5;`,
Expected: []sql.Row{{nil}},
},
{
// Negative indices count from the end.
Query: `SELECT '[10,20,30]'::jsonb -> -1;`,
Expected: []sql.Row{{`30`}},
},
{
Query: `SELECT '[10,20,30]'::jsonb -> -3;`,
Expected: []sql.Row{{`10`}},
},
{
// Out-of-range negative index returns SQL NULL.
Query: `SELECT '[10,20,30]'::jsonb -> -5;`,
Expected: []sql.Row{{nil}},
},
{
// Indexing a non-array returns SQL NULL.
Query: `SELECT '{"a":1}'::jsonb -> 0;`,
Expected: []sql.Row{{nil}},
},
{
// Indexing a scalar returns SQL NULL.
Query: `SELECT '42'::jsonb -> 0;`,
Expected: []sql.Row{{nil}},
},
{
// Nested object element survives the lookup with full
// structure intact.
Query: `SELECT '[{"a":1},{"b":2}]'::jsonb -> 1;`,
Expected: []sql.Row{{`{"b": 2}`}},
},
},
},
{
Name: "json_array_element returns array element",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '[10,20,30]'::json -> 1;`,
Expected: []sql.Row{{`20`}},
},
{
Query: `SELECT '[10,20,30]'::json -> -1;`,
Expected: []sql.Row{{`30`}},
},
{
Query: `SELECT '[10,20,30]'::json -> 99;`,
Expected: []sql.Row{{nil}},
},
},
},
{
Name: "jsonb_array_element_text returns text representation",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '["alpha","beta"]'::jsonb ->> 0;`,
Expected: []sql.Row{{`alpha`}},
},
{
Query: `SELECT '[10,20,30]'::jsonb ->> -1;`,
Expected: []sql.Row{{`30`}},
},
{
Query: `SELECT '[10,20,30]'::jsonb ->> 99;`,
Expected: []sql.Row{{nil}},
},
},
},
})
}
// TestJsonExtractPath exercises the `#>` operator (jsonb_extract_path /
// json_extract_path) and the text-returning `#>>` variant. The path is a
// text array; each element selects a key on an object or an integer index
// on an array at the current location.
func TestJsonExtractPath(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "jsonb_extract_path follows mixed key/index paths",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":{"b":{"c":1}}}'::jsonb #> '{a,b,c}';`,
Expected: []sql.Row{{`1`}},
},
{
Query: `SELECT '{"a":[10,20,30]}'::jsonb #> '{a,1}';`,
Expected: []sql.Row{{`20`}},
},
{
Query: `SELECT '{"a":[10,20,30]}'::jsonb #> '{a,-1}';`,
Expected: []sql.Row{{`30`}},
},
{
Query: `SELECT '{"a":[10,20]}'::jsonb #> '{a,not-an-int}';`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT '{"a":{"b":1}}'::jsonb #> '{a,missing,c}';`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT '{"a":1}'::jsonb #> '{a,b}';`,
Expected: []sql.Row{{nil}},
},
},
},
{
Name: "jsonb_extract_path_text renders the leaf as text",
Assertions: []ScriptTestAssertion{
{
// String leaf returns the raw string.
Query: `SELECT '{"a":{"b":"hello"}}'::jsonb #>> '{a,b}';`,
Expected: []sql.Row{{`hello`}},
},
{
// Object leaf returns the JSON text of the object.
Query: `SELECT '{"a":{"b":{"c":1}}}'::jsonb #>> '{a,b}';`,
Expected: []sql.Row{{`{"c": 1}`}},
},
{
Query: `SELECT '{"a":[1,2,3]}'::jsonb #>> '{a,2}';`,
Expected: []sql.Row{{`3`}},
},
{
Query: `SELECT '{"a":1}'::jsonb #>> '{missing}';`,
Expected: []sql.Row{{nil}},
},
},
},
{
Name: "json_extract_path follows mixed key/index paths",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":{"b":[10,20]}}'::json #> '{a,b,0}';`,
Expected: []sql.Row{{`10`}},
},
{
Query: `SELECT '{"a":1}'::json #> '{missing}';`,
Expected: []sql.Row{{nil}},
},
},
},
{
Name: "jsonb_extract_path with multi-element text-array paths",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":{"b":42}}'::jsonb #> ARRAY['a','b'];`,
Expected: []sql.Row{{`42`}},
},
{
// A deeper path mixing object keys and an array index.
Query: `SELECT '{"a":{"b":{"c":[10,20]}}}'::jsonb #> ARRAY['a','b','c','1'];`,
Expected: []sql.Row{{`20`}},
},
{
Query: `SELECT '{"a":{"b":42}}'::jsonb #>> ARRAY['a','b'];`,
Expected: []sql.Row{{`42`}},
},
{
// The string 'NULL' is an ordinary key, distinct from a SQL
// NULL element: both the ARRAY['NULL'] form and the quoted
// '{"NULL"}' literal select the key named "NULL".
Query: `SELECT '{"NULL":7}'::jsonb #> ARRAY['NULL'];`,
Expected: []sql.Row{{`7`}},
},
{
Query: `SELECT '{"NULL":7}'::jsonb #> '{"NULL"}';`,
Expected: []sql.Row{{`7`}},
},
},
},
{
Name: "jsonb_extract_path returns NULL for NULL path elements",
Assertions: []ScriptTestAssertion{
{
// NULL as the trailing element.
Query: `SELECT '{"a":{"b":42}}'::jsonb #> ARRAY['a',NULL];`,
Expected: []sql.Row{{nil}},
},
{
// NULL as the leading element.
Query: `SELECT '{"a":{"b":42}}'::jsonb #> ARRAY[NULL,'b'];`,
Expected: []sql.Row{{nil}},
},
{
// NULL element in the middle of an otherwise valid path.
Query: `SELECT '{"a":{"b":42}}'::jsonb #> ARRAY['a',NULL,'b'];`,
Expected: []sql.Row{{nil}},
},
{
// Unquoted NULL in the '{...}' literal is a SQL NULL element.
Query: `SELECT '{"a":{"b":42}}'::jsonb #> '{a,NULL,b}';`,
Expected: []sql.Row{{nil}},
},
{
// A single unquoted NULL element, even when a key named
// "NULL" exists, still yields NULL.
Query: `SELECT '{"NULL":7}'::jsonb #> '{NULL}';`,
Expected: []sql.Row{{nil}},
},
{
// The text-returning #>> variant behaves the same way.
Query: `SELECT '{"a":{"b":42}}'::jsonb #>> ARRAY['a',NULL];`,
Expected: []sql.Row{{nil}},
},
{
// A NULL array operand (vs. a NULL element) is NULL via the
// function being strict.
Query: `SELECT '{"a":{"b":42}}'::jsonb #> NULL::text[];`,
Expected: []sql.Row{{nil}},
},
},
},
{
// The json (non-binary) variants resolve through json_extract_path /
// json_extract_path_text and must match the jsonb behavior above.
Name: "json_extract_path with text-array paths and NULL elements",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":{"b":42}}'::json #> ARRAY['a','b'];`,
Expected: []sql.Row{{`42`}},
},
{
Query: `SELECT '{"a":{"b":42}}'::json #>> ARRAY['a','b'];`,
Expected: []sql.Row{{`42`}},
},
{
Query: `SELECT '{"a":{"b":42}}'::json #> ARRAY['a',NULL];`,
Expected: []sql.Row{{nil}},
},
{
Query: `SELECT '{"a":{"b":42}}'::json #>> ARRAY[NULL,'b'];`,
Expected: []sql.Row{{nil}},
},
},
},
})
}
// TestJsonExists exercises the `?`, `?|`, and `?&` operators
// (jsonb_exists / jsonb_exists_any / jsonb_exists_all). For object operands
// the optimized path tests for the key via types.LookupJSONValue; for arrays
// and scalars the existing materialized check is used.
func TestJsonExists(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "jsonb_exists (?) tests key/element presence",
Assertions: []ScriptTestAssertion{
{
// Object: key exists.
Query: `SELECT '{"a":1,"b":2}'::jsonb ? 'a';`,
Expected: []sql.Row{{"t"}},
},
{
// Object: missing key.
Query: `SELECT '{"a":1,"b":2}'::jsonb ? 'z';`,
Expected: []sql.Row{{"f"}},
},
{
// Object: key whose value is JSON null still counts as
// existing.
Query: `SELECT '{"a":null}'::jsonb ? 'a';`,
Expected: []sql.Row{{"t"}},
},
{
// Array: text matches a string element.
Query: `SELECT '["alpha","beta","gamma"]'::jsonb ? 'beta';`,
Expected: []sql.Row{{"t"}},
},
{
// Array: text does not match any element.
Query: `SELECT '["alpha","beta"]'::jsonb ? 'gamma';`,
Expected: []sql.Row{{"f"}},
},
{
// Array: matching only on string elements, not numbers.
Query: `SELECT '[1,2,3]'::jsonb ? '1';`,
Expected: []sql.Row{{"f"}},
},
{
// Scalar string equality.
Query: `SELECT '"hello"'::jsonb ? 'hello';`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT '"hello"'::jsonb ? 'world';`,
Expected: []sql.Row{{"f"}},
},
{
// Non-string scalar never matches.
Query: `SELECT '42'::jsonb ? '42';`,
Expected: []sql.Row{{"f"}},
},
},
},
{
Name: "jsonb_exists_any (?|) tests presence of any key",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":1,"b":2}'::jsonb ?| ARRAY['x','b'];`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT '{"a":1,"b":2}'::jsonb ?| ARRAY['x','y'];`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT '["a","b","c"]'::jsonb ?| ARRAY['x','b'];`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT '["a","b","c"]'::jsonb ?| ARRAY['x','y'];`,
Expected: []sql.Row{{"f"}},
},
},
},
{
Name: "jsonb_exists_all (?&) tests presence of all keys",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{"a":1,"b":2,"c":3}'::jsonb ?& ARRAY['a','b'];`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT '{"a":1,"b":2}'::jsonb ?& ARRAY['a','missing'];`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT '["a","b","c"]'::jsonb ?& ARRAY['a','b'];`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT '["a","b"]'::jsonb ?& ARRAY['a','missing'];`,
Expected: []sql.Row{{"f"}},
},
},
},
})
}
// TestJsonLargeDocumentAccess exercises the same operators against JSONB
// values that are stored in a table column. Documents that exceed ~4 KB are
// stored as out-of-band IndexedJsonDocument values by Dolt's storage layer,
// which implements the SearchableJSON and ComparableJSON interfaces; this
// test ensures the optimized lookup paths in jsonb_object_field,
// jsonb_array_element, jsonb_extract_path, and jsonb_exists* still produce
// correct results when fed through the indexed representation.
func TestJsonLargeDocumentAccess(t *testing.T) {
largeObj := makeLargeJSONObject(100) // ~8 KB
largeArr := makeLargeJSONArray(80) // ~5 KB
RunScripts(t, []ScriptTest{
{
Name: "JSONB operators on large stored object (>4 KB)",
SetUpScript: []string{
`CREATE TABLE bigobj (id INT PRIMARY KEY, doc JSONB)`,
`INSERT INTO bigobj (id, doc) VALUES (1, '` + largeObj + `'::jsonb)`,
},
Assertions: []ScriptTestAssertion{
{
// Sanity check: the stored document is larger than 4 KB,
// which exercises the indexed JSON document path.
Query: `SELECT length(doc::text) > 4096 FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{"t"}},
},
{
// jsonb_object_field on a stored indexed document.
Query: `SELECT doc -> 'k_0037' ->> 'name' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{`value_0037`}},
},
{
// First key at the start of the document.
Query: `SELECT doc -> 'k_0000' ->> 'name' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{`value_0000`}},
},
{
// Last key at the end of the document.
Query: `SELECT doc -> 'k_0099' ->> 'name' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{`value_0099`}},
},
{
// Missing key returns SQL NULL.
Query: `SELECT doc -> 'no_such_key' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{nil}},
},
{
// Numeric value via ->>.
Query: `SELECT doc -> 'k_0042' ->> 'n' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{`42`}},
},
{
// jsonb_extract_path through several levels of an indexed
// document, ending at an array element.
Query: `SELECT doc #>> '{k_0010, tags, 2}' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{`tag-c`}},
},
{
// jsonb_extract_path with a negative index hits the
// negative-index fallback path inside extractOneJsonPathStep.
Query: `SELECT doc #>> '{k_0050, tags, -1}' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{`tag-e`}},
},
{
// Missing intermediate path returns SQL NULL.
Query: `SELECT doc #> '{k_0001, missing}' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{nil}},
},
{
// jsonb_exists on a stored indexed document.
Query: `SELECT doc ? 'k_0017' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT doc ? 'no_such_key' FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{"f"}},
},
{
// jsonb_exists_any with a mix of present and missing keys.
Query: `SELECT doc ?| ARRAY['no_such_key', 'k_0005'] FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT doc ?| ARRAY['nope_1', 'nope_2'] FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{"f"}},
},
{
// jsonb_exists_all where every key is present.
Query: `SELECT doc ?& ARRAY['k_0001', 'k_0099'] FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT doc ?& ARRAY['k_0001', 'no_such_key'] FROM bigobj WHERE id = 1;`,
Expected: []sql.Row{{"f"}},
},
},
},
{
Name: "JSONB operators on large stored array (>4 KB)",
SetUpScript: []string{
`CREATE TABLE bigarr (id INT PRIMARY KEY, doc JSONB)`,
`INSERT INTO bigarr (id, doc) VALUES (1, '` + largeArr + `'::jsonb)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT length(doc::text) > 4096 FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{"t"}},
},
{
// Positive index hits the SearchableJSON fast path.
Query: `SELECT doc -> 17 ->> 'label' FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{`row_0017`}},
},
{
// First element.
Query: `SELECT doc -> 0 ->> 'label' FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{`row_0000`}},
},
{
// Last element via positive index.
Query: `SELECT doc -> 79 ->> 'label' FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{`row_0079`}},
},
{
// Negative index hits the materialized fallback path,
// which must agree with the optimized path on the answer.
Query: `SELECT doc -> -1 ->> 'label' FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{`row_0079`}},
},
{
// Out-of-range positive index.
Query: `SELECT doc -> 1000 FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{nil}},
},
{
// jsonb_extract_path on an array followed by an object key.
Query: `SELECT doc #>> '{42, label}' FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{`row_0042`}},
},
{
// jsonb_extract_path into a nested array element.
Query: `SELECT doc #>> '{42, payload, 3}' FROM bigarr WHERE id = 1;`,
Expected: []sql.Row{{`d`}},
},
},
},
{
Name: "jsonb_extract_path on large stored object with numeric keys (>4 KB)",
SetUpScript: []string{
`CREATE TABLE numkeys (id INT PRIMARY KEY, doc JSONB)`,
`INSERT INTO numkeys (id, doc) VALUES (1, '` + makeLargeJSONObjectWithNumericKeys() + `'::jsonb)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT length(doc::text) > 4096 FROM numkeys WHERE id = 1;`,
Expected: []sql.Row{{"t"}},
},
{
// A numeric path element on an object is first guessed as an
// array index ([0]), which the indexed lookup rejects, so it
// must fall back to the object key "0".
Query: `SELECT doc #>> '{nums, 0}' FROM numkeys WHERE id = 1;`,
Expected: []sql.Row{{`zero`}},
},
{
Query: `SELECT doc #>> '{nums, 2}' FROM numkeys WHERE id = 1;`,
Expected: []sql.Row{{`two`}},
},
{
// Missing numeric key returns SQL NULL after the fallback.
Query: `SELECT doc #> '{nums, 5}' FROM numkeys WHERE id = 1;`,
Expected: []sql.Row{{nil}},
},
{
// A genuine object key + array index path through the same
// large document still resolves on the single-lookup path.
Query: `SELECT doc #>> '{k_0001, tags, 0}' FROM numkeys WHERE id = 1;`,
Expected: []sql.Row{{`tag-a`}},
},
},
},
})
}
// TestJsonbNumericCasts exercises the jsonb → numeric type casts in
// server/cast/jsonb.go. The integer casts must round half-to-even (matching
// Postgres' numeric → integer rules) and return an out-of-range error when
// the rounded value doesn't fit in the destination type. The float casts
// must reject values too large to represent as a finite value in the
// destination floating-point type. The non-numeric jsonb cases (object,
// array, string, boolean, null) must each error with a type-specific
// message.
func TestJsonbNumericCasts(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "jsonb -> int2: rounding, boundaries, and out-of-range",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '12345'::jsonb::int2;`,
Expected: []sql.Row{{int16(12345)}},
},
{
Query: `SELECT '-12345'::jsonb::int2;`,
Expected: []sql.Row{{int16(-12345)}},
},
{
// Half-to-even rounding: 0.4 always rounds down.
Query: `SELECT '12345.4'::jsonb::int2;`,
Expected: []sql.Row{{int16(12345)}},
},
{
// 12345.5 → 12346 (round half to even, 12346 is even).
Query: `SELECT '12345.5'::jsonb::int2;`,
Expected: []sql.Row{{int16(12346)}},
},
{
// 12346.5 → 12346 (round half to even, 12346 is even).
Query: `SELECT '12346.5'::jsonb::int2;`,
Expected: []sql.Row{{int16(12346)}},
},
{
// Boundary values that fit exactly.
Query: `SELECT '32767'::jsonb::int2;`,
Expected: []sql.Row{{int16(32767)}},
},
{
Query: `SELECT '-32768'::jsonb::int2;`,
Expected: []sql.Row{{int16(-32768)}},
},
{
// Fractional value that rounds down into range.
Query: `SELECT '32767.4'::jsonb::int2;`,
Expected: []sql.Row{{int16(32767)}},
},
{
// One past the upper bound.
Query: `SELECT '32768'::jsonb::int2;`,
ExpectedErr: "smallint out of range",
},
{
// 32767.5 rounds to 32768, which is out of range.
Query: `SELECT '32767.5'::jsonb::int2;`,
ExpectedErr: "smallint out of range",
},
{
Query: `SELECT '-32769'::jsonb::int2;`,
ExpectedErr: "smallint out of range",
},
{
// Values far outside the int16 range still produce a
// clean out-of-range error rather than an int64 overflow.
Query: `SELECT '1e20'::jsonb::int2;`,
ExpectedErr: "smallint out of range",
},
},
},
{
Name: "jsonb -> int4: rounding, boundaries, and out-of-range",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '0'::jsonb::int4;`,
Expected: []sql.Row{{int32(0)}},
},
{
Query: `SELECT '2147483647'::jsonb::int4;`,
Expected: []sql.Row{{int32(2147483647)}},
},
{
Query: `SELECT '-2147483648'::jsonb::int4;`,
Expected: []sql.Row{{int32(-2147483648)}},
},
{
// Fractional that rounds down into range.
Query: `SELECT '2147483647.4'::jsonb::int4;`,
Expected: []sql.Row{{int32(2147483647)}},
},
{
Query: `SELECT '2147483648'::jsonb::int4;`,
ExpectedErr: "integer out of range",
},
{
Query: `SELECT '-2147483649'::jsonb::int4;`,
ExpectedErr: "integer out of range",
},
{
Query: `SELECT '1e20'::jsonb::int4;`,
ExpectedErr: "integer out of range",
},
},
},
{
Name: "jsonb -> int8: rounding, boundaries, and out-of-range",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '0'::jsonb::int8;`,
Expected: []sql.Row{{int64(0)}},
},
{
// 2^53 - 1: the largest integer that survives the
// round-trip through float64 that the jsonb parser
// currently performs on input.
Query: `SELECT '9007199254740991'::jsonb::int8;`,
Expected: []sql.Row{{int64(9007199254740991)}},
},
{
Query: `SELECT '-9007199254740991'::jsonb::int8;`,
Expected: []sql.Row{{int64(-9007199254740991)}},
},
{
// Large value that doesn't fit in int64 must error
// rather than silently truncating.
Query: `SELECT '1e20'::jsonb::int8;`,
ExpectedErr: "bigint out of range",
},
{
Query: `SELECT '-1e20'::jsonb::int8;`,
ExpectedErr: "bigint out of range",
},
},
},
{
Name: "jsonb -> float4: out-of-range",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '0'::jsonb::float4;`,
Expected: []sql.Row{{float32(0)}},
},
{
Query: `SELECT '1.5'::jsonb::float4;`,
Expected: []sql.Row{{float32(1.5)}},
},
{
// Just inside float32 max (~3.4028235e38).
Query: `SELECT '3.4e38'::jsonb::float4;`,
Expected: []sql.Row{{float32(3.4e38)}},
},
{
// Just outside float32 max.
Query: `SELECT '3.5e38'::jsonb::float4;`,
ExpectedErr: "out of range",
},
{
Query: `SELECT '-3.5e38'::jsonb::float4;`,
ExpectedErr: "out of range",
},
{
Query: `SELECT '1e40'::jsonb::float4;`,
ExpectedErr: "out of range",
},
},
},
{
Name: "jsonb -> float8 round-trips finite values",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '0'::jsonb::float8;`,
Expected: []sql.Row{{float64(0)}},
},
{
Query: `SELECT '1.5'::jsonb::float8;`,
Expected: []sql.Row{{float64(1.5)}},
},
{
// Larger value that still fits in float64.
Query: `SELECT '1e300'::jsonb::float8;`,
Expected: []sql.Row{{float64(1e300)}},
},
// Out-of-range float8 values can't be tested via a jsonb
// literal: the jsonb parser itself rejects '1e400' because
// it cannot be represented in the float64 used for input
// parsing.
},
},
{
Name: "jsonb -> numeric: preserves precision",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '12345'::jsonb::numeric;`,
Expected: []sql.Row{{Numeric("12345")}},
},
{
Query: `SELECT '12345.67'::jsonb::numeric;`,
Expected: []sql.Row{{Numeric("12345.67")}},
},
{
Query: `SELECT '-12345.67'::jsonb::numeric;`,
Expected: []sql.Row{{Numeric("-12345.67")}},
},
},
},
{
Name: "jsonb non-numeric values reject numeric casts",
Assertions: []ScriptTestAssertion{
{
Query: `SELECT '{}'::jsonb::int4;`,
ExpectedErr: "cannot cast jsonb object",
},
{
Query: `SELECT '[]'::jsonb::int4;`,
ExpectedErr: "cannot cast jsonb array",
},
{
Query: `SELECT '"42"'::jsonb::int4;`,
ExpectedErr: "cannot cast jsonb string",
},
{
Query: `SELECT 'true'::jsonb::int4;`,
ExpectedErr: "cannot cast jsonb boolean",
},
{
Query: `SELECT 'null'::jsonb::int4;`,
ExpectedErr: "cannot cast jsonb null",
},
{
Query: `SELECT '{}'::jsonb::float4;`,
ExpectedErr: "cannot cast jsonb object",
},
{
Query: `SELECT '[]'::jsonb::numeric;`,
ExpectedErr: "cannot cast jsonb array",
},
},
},
})
}
+161
View File
@@ -0,0 +1,161 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"context"
"testing"
"github.com/dolthub/go-mysql-server/sql"
gmstypes "github.com/dolthub/go-mysql-server/sql/types"
"github.com/stretchr/testify/require"
"github.com/dolthub/doltgresql/server/functions"
pgtypes "github.com/dolthub/doltgresql/server/types"
)
// testAnyWrapper simulates *val.ExtendedValueWrapper — a sql.AnyWrapper that wraps a
// sql.JSONWrapper rather than a plain string. This is what Dolt returns when reading a
// large JSONB value that was stored out-of-band with the old ExtendedAdaptiveEnc encoding
// (used before JsonAdaptiveEnc was enabled for json/jsonb columns). UnwrapAny() on such a
// wrapper calls the child handler's DeserializeValue, which for JSONB returns a
// gmstypes.JSONDocument, not a string.
type testAnyWrapper struct {
inner interface{}
}
func (w *testAnyWrapper) UnwrapAny(_ context.Context) (interface{}, error) { return w.inner, nil }
func (w *testAnyWrapper) IsExactLength() bool { return true }
func (w *testAnyWrapper) MaxByteLength() int64 { return 1000 }
func (w *testAnyWrapper) Compare(_ context.Context, _ interface{}) (int, bool, error) {
return 0, false, nil
}
func (w *testAnyWrapper) Hash() interface{} { return nil }
// TestJsonbOutCallableWithAnyWrapper verifies that jsonb_out_callable handles a
// sql.AnyWrapper value. Large JSONB values in databases created before JsonAdaptiveEnc
// was enabled (using ExtendedAdaptiveEnc instead) are returned from storage as a
// *val.ExtendedValueWrapper, which implements sql.AnyWrapper. Its UnwrapAny() yields a
// gmstypes.JSONDocument — so jsonb_out_callable must unwrap and then handle the document,
// not panic or return an "unexpected type" error.
func TestJsonbOutCallableWithAnyWrapper(t *testing.T) {
ctx := sql.NewEmptyContext()
tests := []struct {
name string
jsonStr string
}{
{"object", `{"key": "value", "num": 42}`},
{"array", `[1, 2, 3]`},
{"nested", `{"a": {"b": [true, null]}}`},
{"string_scalar", `"hello"`},
{"number_scalar", `99`},
{"bool_scalar", `false`},
{"null_scalar", `null`},
// large_object exercises the out-of-band storage path: a document large
// enough that the old ExtendedAdaptiveEnc encoding would store it off-page.
{"large_object", makeLargeJSONObject(100)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jsonDoc := gmstypes.MustJSON(tt.jsonStr)
result, err := functions.JsonbOutCallable(ctx, [2]*pgtypes.DoltgresType{}, &testAnyWrapper{inner: jsonDoc})
require.NoError(t, err, "jsonb_out_callable must handle sql.AnyWrapper wrapping a JSONDocument")
require.NotEmpty(t, result)
})
}
}
// TestJsonOutCallableWithAnyWrapper is the json_out equivalent of TestJsonbOutCallableWithAnyWrapper.
// json_out_callable has the same AnyWrapper gap as jsonb_out_callable.
func TestJsonOutCallableWithAnyWrapper(t *testing.T) {
ctx := sql.NewEmptyContext()
tests := []struct {
name string
jsonStr string
}{
{"object", `{"key": "value", "num": 42}`},
{"array", `[1, 2, 3]`},
{"nested", `{"a": {"b": [true, null]}}`},
{"string_scalar", `"hello"`},
{"number_scalar", `99`},
{"bool_scalar", `false`},
{"null_scalar", `null`},
{"large_object", makeLargeJSONObject(100)},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jsonDoc := gmstypes.MustJSON(tt.jsonStr)
result, err := functions.JsonOutCallable(ctx, [2]*pgtypes.DoltgresType{}, &testAnyWrapper{inner: jsonDoc})
require.NoError(t, err, "json_out_callable must handle sql.AnyWrapper wrapping a JSONDocument")
require.NotEmpty(t, result)
})
}
}
// TestJsonbSerializeWithAnyWrapper verifies that types.JsonB.SerializeValue handles a
// sql.AnyWrapper that wraps a JSONDocument containing *apd.Decimal values. This
// represents the combined Bug 1 + Bug 2 production failure path:
//
// 1. A large legacy JSONB value is read from Dolt → *val.ExtendedValueWrapper (AnyWrapper).
// 2. The wrapper is passed to serializeTypeJsonB (e.g. during replication or export).
// 3. sql.UnwrapAny extracts a gmstypes.JSONDocument.
// 4. ToInterface() returns the .Val, which after the jsonValueToInterface bugfix
// contains *apd.Decimal for numeric fields.
// 5. ConvertToJsonDocument(*apd.Decimal) must handle that type — previously it did not.
func TestJsonbSerializeWithAnyWrapper(t *testing.T) {
ctx := sql.NewEmptyContext()
tests := []struct {
name string
inner gmstypes.JSONDocument
}{
{
"scalar_decimal",
gmstypes.JSONDocument{Val: mustDecimal("3.14")},
},
{
"object_with_decimal",
gmstypes.JSONDocument{Val: map[string]any{
"n": mustDecimal("42"),
"s": "hello",
}},
},
{
"array_with_decimals",
gmstypes.JSONDocument{Val: []any{mustDecimal("1"), mustDecimal("2"), mustDecimal("3")}},
},
{
"nested_decimals",
gmstypes.JSONDocument{Val: map[string]any{
"a": map[string]any{"b": mustDecimal("-9.9")},
}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wrapper := &testAnyWrapper{inner: tt.inner}
_, err := pgtypes.JsonB.SerializeValue(ctx, wrapper)
require.NoError(t, err,
"SerializeValue must handle an AnyWrapper wrapping a JSONDocument with *apd.Decimal")
})
}
}
+286
View File
@@ -0,0 +1,286 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"strings"
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
// jsonbLexicalOrder is the 40 test values in ascending comparison order as determined
// by CompareJSON (type precedence: null < number < string < object < array < boolean).
// Objects are ordered by: shared-key value comparison, then key-count, then least
// non-shared key lexicographically. Arrays are ordered element-by-element; shorter
// wins when all shared positions are equal.
var jsonbLexicalOrder = []string{
`null`,
`-1`,
`0`,
`1`,
`2`,
`3.14`,
`42`,
`100`,
`9999`,
`"a"`,
`"ab"`,
`"abc"`,
`"b"`,
`"foo"`,
`"hello"`,
`"hello world"`,
`"longer string value"`,
`"z"`,
`{}`,
`{"z":null}`,
`{"x":{"y":1}}`,
`{"name":"test","value":42}`,
`{"b":2}`,
`{"aa":1}`,
`{"a":1}`,
`{"a":1,"b":2}`,
`{"a":1,"b":2,"c":3}`,
`{"a":{"b":{"c":1}}}`,
`[]`,
`[null]`,
`[1]`,
`[1,2]`,
`[1,2,3]`,
`["a"]`,
`["a","b","c"]`,
`[[1,2],[3,4]]`,
`[false]`,
`[true]`,
`false`,
`true`,
}
// jsonbExpectedOutput is the JSONB-normalized text representation of each value in
// jsonbLexicalOrder.
var jsonbExpectedOutput = []sql.Row{
{nil},
{"-1"},
{"0"},
{"1"},
{"2"},
{"3.14"},
{"42"},
{"100"},
{"9999"},
{"\"a\""},
{"\"ab\""},
{"\"abc\""},
{"\"b\""},
{"\"foo\""},
{"\"hello\""},
{"\"hello world\""},
{"\"longer string value\""},
{"\"z\""},
{"{}"},
{"{\"z\":null}"},
{"{\"x\":{\"y\":1}}"},
{"{\"name\":\"test\",\"value\":42}"},
{"{\"b\":2}"},
{"{\"aa\":1}"},
{"{\"a\":1}"},
{"{\"a\":1,\"b\":2}"},
{"{\"a\":1,\"b\":2,\"c\":3}"},
{"{\"a\":{\"b\":{\"c\":1}}}"},
{"[]"},
{"[null]"},
{"[1]"},
{"[1,2]"},
{"[1,2,3]"},
{"[\"a\"]"},
{"[\"a\",\"b\",\"c\"]"},
{"[[1,2],[3,4]]"},
{"[false]"},
{"[true]"},
{"false"},
{"true"},
}
// TestJsonBPairwiseLessThan walks jsonbLexicalOrder and asserts that every
// consecutive pair satisfies the < operator: SELECT 'a'::jsonb < 'b'::jsonb = t.
// It also inserts all values into an indexed JSONB column and verifies that
// ORDER BY returns them in exactly the same order.
func TestJsonBPairwiseLessThan(t *testing.T) {
// 39 pairwise < assertions
assertions := make([]ScriptTestAssertion, 0, len(jsonbLexicalOrder))
for i := 0; i < len(jsonbLexicalOrder)-1; i++ {
a, b := jsonbLexicalOrder[i], jsonbLexicalOrder[i+1]
assertions = append(assertions, ScriptTestAssertion{
Query: `SELECT '` + a + `'::jsonb < '` + b + `'::jsonb`,
Expected: []sql.Row{{"t"}},
})
}
// ORDER BY assertion: index scan must return rows in the same order
assertions = append(assertions, ScriptTestAssertion{
Query: `SELECT val FROM jorder ORDER BY val`,
Expected: jsonbExpectedOutput,
})
// Build the VALUES list for the INSERT
vals := make([]string, len(jsonbLexicalOrder))
for i, v := range jsonbLexicalOrder {
vals[i] = `('` + v + `')`
}
RunScriptsWithoutNormalization(t, []ScriptTest{
{
Name: "JSONB pairwise less-than along lexical order",
SetUpScript: []string{
`CREATE TABLE jorder (val JSON NOT NULL)`,
`CREATE INDEX jorder_val_idx ON jorder (val)`,
`INSERT INTO jorder (val) VALUES ` + strings.Join(vals, ", "),
},
Assertions: assertions,
},
})
}
// TestJsonBIndexSortConsistency verifies that, for an indexed JSONB column, the sort
// order produced by ORDER BY is consistent with the < and > comparison operators.
//
// Contract: for every pair of elements (a, b) stored in the table,
// - if a < b (operator), then a must appear before b in ORDER BY
// - if a > b (operator), then a must appear after b in ORDER BY
//
// The test uses a variety of JSON documents — nulls, booleans, numbers, strings,
// arrays, and objects at different nesting depths and key counts — to maximise
// the chance of exposing any mismatch between the index byte-encoding sort order
// and the semantic comparison order used by < / >.
func TestJsonBIndexSortConsistency(t *testing.T) {
RunScriptsWithoutNormalization(t, []ScriptTest{
{
Name: "JSONB index sort order matches < and > operators",
SetUpScript: []string{
`CREATE TABLE jtest (id SERIAL PRIMARY KEY, val JSONB NOT NULL)`,
`CREATE INDEX jtest_val_idx ON jtest (val)`,
// Diverse documents: null, booleans, numbers (various magnitudes /
// signs / decimals), strings (short to long), arrays (empty,
// scalars, nested), objects (empty, single key, multi-key, nested).
`INSERT INTO jtest (val) VALUES
('null'),
('false'),
('true'),
('-1'),
('0'),
('1'),
('2'),
('3.14'),
('42'),
('100'),
('9999'),
('"a"'),
('"b"'),
('"z"'),
('"ab"'),
('"abc"'),
('"foo"'),
('"hello"'),
('"hello world"'),
('"longer string value"'),
('[]'),
('[1]'),
('[1,2]'),
('[1,2,3]'),
('["a"]'),
('[null]'),
('[false]'),
('[true]'),
('["a","b","c"]'),
('[[1,2],[3,4]]'),
('{}'),
('{"a":1}'),
('{"b":2}'),
('{"aa":1}'),
('{"a":1,"b":2}'),
('{"a":1,"b":2,"c":3}'),
('{"x":{"y":1}}'),
('{"name":"test","value":42}'),
('{"a":{"b":{"c":1}}}'),
('{"z":null}')`,
},
Assertions: []ScriptTestAssertion{
{
// Transitivity check: if a < b and b < c then a < c must hold.
// A non-zero count reveals a comparison function that is not a
// valid total order, which makes any index ordering undefined.
Query: `SELECT COUNT(*) FROM jtest a
JOIN jtest b ON a.val < b.val
JOIN jtest c ON b.val < c.val
WHERE NOT (a.val < c.val)`,
Expected: []sql.Row{{int64(0)}},
},
{
// Converse transitivity via >: if a > b and b > c then a > c.
Query: `SELECT COUNT(*) FROM jtest a
JOIN jtest b ON a.val > b.val
JOIN jtest c ON b.val > c.val
WHERE NOT (a.val > c.val)`,
Expected: []sql.Row{{int64(0)}},
},
{
// Adjacent-pair check using LAG: for every consecutive pair
// (prev, curr) in the ORDER BY sequence, prev < curr must hold.
// This catches any local inversion in the sort order.
Query: `SELECT COUNT(*) FROM (
SELECT val,
LAG(val) OVER (ORDER BY val) AS prev_val
FROM jtest
) t
WHERE prev_val IS NOT NULL AND NOT (prev_val < val)`,
Expected: []sql.Row{{int64(0)}},
},
{
// LEAD version of the same check: curr < next must hold for
// every consecutive pair.
Query: `SELECT COUNT(*) FROM (
SELECT val,
LEAD(val) OVER (ORDER BY val) AS next_val
FROM jtest
) t
WHERE next_val IS NOT NULL AND NOT (val < next_val)`,
Expected: []sql.Row{{int64(0)}},
},
{
// Antisymmetry: if a < b then NOT (a > b).
// A non-zero count indicates the comparison function is not
// antisymmetric, which would make index ordering undefined.
Query: `SELECT COUNT(*) FROM jtest a
JOIN jtest b ON a.val < b.val
WHERE a.val > b.val`,
Expected: []sql.Row{{int64(0)}},
},
{
// Totality / strict total order: for every unequal pair,
// exactly one of a < b or b < a must hold.
// If neither holds for a pair of unequal values the relation
// is not a total order and the index cannot sort it correctly.
Query: `SELECT COUNT(*) FROM jtest a
JOIN jtest b ON a.id < b.id
WHERE a.val <> b.val
AND NOT (a.val < b.val)
AND NOT (b.val < a.val)`,
Expected: []sql.Row{{int64(0)}},
},
},
},
})
}
+349
View File
@@ -0,0 +1,349 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/cockroachdb/apd/v3"
gmstypes "github.com/dolthub/go-mysql-server/sql/types"
"github.com/stretchr/testify/require"
"github.com/dolthub/doltgresql/server/types"
)
// TestLegacyJsonBRoundTrip verifies that JSONB values written in the old ExtendedEnc format
// (used before JsonAdaptiveEnc was introduced for jsonb columns) deserialize correctly.
// Each JSON value type is tested to ensure no internal JsonValue* types leak into the result.
func TestLegacyJsonBRoundTrip(t *testing.T) {
tests := []struct {
name string
input any // value placed into JSONDocument.Val before serialization
expected any // expected JSONDocument.Val after deserialization
}{
// ── Primitives ────────────────────────────────────────────────────────────────
{
name: "null",
input: nil,
expected: nil,
},
{
name: "bool_true",
input: true,
expected: true,
},
{
name: "bool_false",
input: false,
expected: false,
},
{
name: "string",
input: "hello",
expected: "hello",
},
{
name: "string_empty",
input: "",
expected: "",
},
{
name: "string_unicode",
input: "こんにちは",
expected: "こんにちは",
},
// Numbers: the primary compatibility concern — must come back as *apd.Decimal,
// not the internal JsonValueNumber type.
{
name: "number_zero",
input: json.Number("0"),
expected: mustDecimal("0"),
},
{
name: "number_integer",
input: json.Number("42"),
expected: mustDecimal("42"),
},
{
name: "number_negative_integer",
input: json.Number("-7"),
expected: mustDecimal("-7"),
},
{
name: "number_float",
input: json.Number("3.14"),
expected: mustDecimal("3.14"),
},
{
name: "number_negative_float",
input: json.Number("-2.718"),
expected: mustDecimal("-2.718"),
},
{
name: "number_large_integer",
input: json.Number("99999999999999999999"),
expected: mustDecimal("99999999999999999999"),
},
{
name: "number_high_precision",
input: json.Number("1.23456789012345678901"),
expected: mustDecimal("1.23456789012345678901"),
},
// ── Empty containers ──────────────────────────────────────────────────────────
{
name: "object_empty",
input: map[string]any{},
expected: map[string]any{},
},
{
name: "array_empty",
input: []any{},
expected: []any{},
},
// ── Objects: one test per value type ─────────────────────────────────────────
{
name: "object_string_val",
input: map[string]any{"k": "v"},
expected: map[string]any{"k": "v"},
},
{
name: "object_number_val",
input: map[string]any{"n": json.Number("99")},
expected: map[string]any{"n": mustDecimal("99")},
},
{
name: "object_bool_val",
input: map[string]any{"b": true},
expected: map[string]any{"b": true},
},
{
name: "object_null_val",
input: map[string]any{"n": nil},
expected: map[string]any{"n": nil},
},
{
name: "object_mixed_vals",
input: map[string]any{
"s": "hello", "n": json.Number("1.5"), "b": false, "z": nil,
},
expected: map[string]any{
"s": "hello", "n": mustDecimal("1.5"), "b": false, "z": nil,
},
},
// ── Arrays: one test per value type ──────────────────────────────────────────
{
name: "array_strings",
input: []any{"a", "b", "c"},
expected: []any{"a", "b", "c"},
},
{
name: "array_numbers",
input: []any{json.Number("1"), json.Number("2"), json.Number("3")},
expected: []any{mustDecimal("1"), mustDecimal("2"), mustDecimal("3")},
},
{
name: "array_bools",
input: []any{true, false, true},
expected: []any{true, false, true},
},
{
name: "array_nulls",
input: []any{nil, nil},
expected: []any{nil, nil},
},
{
name: "array_mixed",
input: []any{json.Number("1"), "two", true, nil},
expected: []any{mustDecimal("1"), "two", true, nil},
},
// ── Nested structures ─────────────────────────────────────────────────────────
{
name: "object_with_nested_array",
input: map[string]any{
"nums": []any{json.Number("1"), json.Number("2")},
"name": "test",
},
expected: map[string]any{
"nums": []any{mustDecimal("1"), mustDecimal("2")},
"name": "test",
},
},
{
name: "array_of_objects",
input: []any{
map[string]any{"x": json.Number("10")},
map[string]any{"x": json.Number("20")},
},
expected: []any{
map[string]any{"x": mustDecimal("10")},
map[string]any{"x": mustDecimal("20")},
},
},
{
name: "deeply_nested",
input: map[string]any{
"a": map[string]any{
"b": []any{
json.Number("1"),
map[string]any{"c": true, "d": json.Number("-9.9")},
[]any{"x", json.Number("0")},
},
},
},
expected: map[string]any{
"a": map[string]any{
"b": []any{
mustDecimal("1"),
map[string]any{"c": true, "d": mustDecimal("-9.9")},
[]any{"x", mustDecimal("0")},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
doc := gmstypes.JSONDocument{Val: tt.input}
// Simulate writing with old Doltgres (ExtendedEnc path calls serializeTypeJsonB).
serialized, err := types.JsonB.SerializeValue(context.Background(), doc)
require.NoError(t, err)
// Simulate reading with new Doltgres (still calls deserializeTypeJsonB for old rows).
result, err := types.JsonB.DeserializeValue(context.Background(), serialized)
require.NoError(t, err)
require.NotNil(t, result)
resultDoc, ok := result.(gmstypes.JSONDocument)
require.True(t, ok)
requireNoInternalJsonTypes(t, resultDoc.Val)
requireJsonEqual(t, tt.expected, resultDoc.Val)
})
}
}
// requireNoInternalJsonTypes recurses through a deserialized JSON value and asserts that no
// internal JsonValue* types have leaked out. Every value must be a native Go type.
func requireNoInternalJsonTypes(t *testing.T, val any) {
t.Helper()
switch v := val.(type) {
case map[string]any:
for _, child := range v {
requireNoInternalJsonTypes(t, child)
}
case []any:
for _, item := range v {
requireNoInternalJsonTypes(t, item)
}
case types.JsonValueNumber:
t.Errorf("number leaked as internal JsonValueNumber; expected *apd.Decimal")
case types.JsonValueString:
t.Errorf("string leaked as internal JsonValueString; expected string")
case types.JsonValueBoolean:
t.Errorf("bool leaked as internal JsonValueBoolean; expected bool")
case types.JsonValueNull:
t.Errorf("null leaked as internal JsonValueNull; expected nil")
case types.JsonValueObject:
t.Errorf("object leaked as internal JsonValueObject; expected map[string]any")
case types.JsonValueArray:
t.Errorf("array leaked as internal JsonValueArray; expected []any")
}
}
// requireJsonEqual recursively compares an expected JSON value tree against actual,
// using string comparison for *apd.Decimal to avoid representation sensitivity.
func requireJsonEqual(t *testing.T, expected, actual any) {
t.Helper()
if expected == nil {
require.Nil(t, actual, "expected nil, got %T: %v", actual, actual)
return
}
switch e := expected.(type) {
case *apd.Decimal:
a, ok := actual.(*apd.Decimal)
require.True(t, ok, "expected *apd.Decimal, got %T: %v", actual, actual)
require.Equal(t, e.String(), a.String())
case map[string]any:
a, ok := actual.(map[string]any)
require.True(t, ok, "expected map[string]any, got %T", actual)
require.Equal(t, len(e), len(a), "object has %d keys, want %d", len(a), len(e))
for k, ev := range e {
av, exists := a[k]
require.True(t, exists, "key %q missing from result", k)
requireJsonEqual(t, ev, av)
}
case []any:
a, ok := actual.([]any)
require.True(t, ok, "expected []any, got %T", actual)
require.Equal(t, len(e), len(a), "array has %d elements, want %d", len(a), len(e))
for i := range e {
requireJsonEqual(t, e[i], a[i])
}
default:
require.Equal(t, expected, actual)
}
}
// TestLegacyJsonBReserialization verifies that a value deserialized from the legacy
// ExtendedEnc format can be re-serialized without error. This exercises the path where
// serializeTypeJsonB receives a JSONDocument whose Val contains *apd.Decimal (the type
// that jsonValueToInterface now returns for JsonValueNumber after the bugfix), and
// ConvertToJsonDocument must handle it.
func TestLegacyJsonBReserialization(t *testing.T) {
tests := []struct {
name string
input any
}{
{"number_integer", json.Number("42")},
{"number_negative", json.Number("-7")},
{"number_float", json.Number("3.14")},
{"number_large", json.Number("99999999999999999999")},
{"object_with_number", map[string]any{"n": json.Number("99")}},
{"array_with_numbers", []any{json.Number("1"), json.Number("2"), json.Number("3")}},
{"nested_number", map[string]any{"a": map[string]any{"b": json.Number("1.5")}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
doc := gmstypes.JSONDocument{Val: tt.input}
// Step 1: serialize (simulates old Doltgres write using the legacy ExtendedEnc path)
serialized, err := types.JsonB.SerializeValue(context.Background(), doc)
require.NoError(t, err)
// Step 2: deserialize (jsonValueToInterface now returns *apd.Decimal for numbers)
deserialized, err := types.JsonB.DeserializeValue(context.Background(), serialized)
require.NoError(t, err)
require.NotNil(t, deserialized)
// Step 3: re-serialize the deserialized value.
_, err = types.JsonB.SerializeValue(context.Background(), deserialized)
require.NoError(t, err, "re-serializing a deserialized legacy JSONB value must not fail")
})
}
}
// mustDecimal parses s into an *apd.Decimal, panicking on failure.
func mustDecimal(s string) *apd.Decimal {
d := new(apd.Decimal)
if err := d.Scan(s); err != nil {
panic(fmt.Sprintf("mustDecimal(%q): %v", s, err))
}
return d
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2024 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestLimitOffset(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "basic limit tests",
SetUpScript: []string{
`CREATE TABLE t (i INT PRIMARY KEY, c int)`,
`INSERT INTO t VALUES (1, 1), (2, 2), (3, 3), (4, 4)`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT * FROM t LIMIT 2`,
Expected: []sql.Row{{1, 1}, {2, 2}},
},
{
Query: `SELECT * FROM t LIMIT $1`,
BindVars: []interface{}{int64(2)},
Expected: []sql.Row{{1, 1}, {2, 2}},
},
{
Query: `SELECT * FROM t LIMIT 2 OFFSET 2`,
Expected: []sql.Row{{3, 3}, {4, 4}},
},
{
Query: `SELECT * FROM t order by c asc LIMIT 2 OFFSET 2`,
Expected: []sql.Row{{3, 3}, {4, 4}},
},
},
},
})
}
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
// TestLocks tests the advisory lock functions, such as pg_try_advisory_lock and pg_advisory_unlock.
func TestAdvisoryLocks(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "basic lock tests",
SetUpScript: []string{
`CREATE USER user1 PASSWORD 'password';`,
},
Assertions: []ScriptTestAssertion{
{
Query: `SELECT pg_advisory_lock(1)`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT pg_try_advisory_lock(2)`,
Expected: []sql.Row{{"t"}},
},
{
// When a different session tries to acquire the same lock, it fails.
Username: "user1",
Password: "password",
Query: `SELECT pg_try_advisory_lock(1)`,
Expected: []sql.Row{{"f"}},
},
{
// When a different session tries to acquire the same lock, it fails.
Username: "user1",
Password: "password",
Query: `SELECT pg_try_advisory_lock(2)`,
Expected: []sql.Row{{"f"}},
},
{
Query: `SELECT pg_advisory_unlock(1)`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT pg_advisory_unlock(2)`,
Expected: []sql.Row{{"t"}},
},
{
Query: `SELECT pg_advisory_unlock(3)`,
Expected: []sql.Row{{"f"}},
},
},
},
})
}
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestMerge(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "simple merge",
SetUpScript: []string{
"CREATE TABLE t1 (a INT, b INT, c INT, PRIMARY KEY (a))",
"CREATE TABLE t2 (a INT, b INT, c INT, PRIMARY KEY (a))",
"INSERT INTO t1 VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)",
"INSERT INTO t2 VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)",
"SELECT DOLT_COMMIT('-Am', 'intial commit')",
"SELECT DOLT_BRANCH('branch1')",
"SELECT DOLT_BRANCH('branch2')",
"SELECT DOLT_CHECKOUT('branch1')",
"INSERT INTO t1 VALUES (10, 11, 12)",
"INSERT INTO t2 VALUES (10, 11, 12)",
"SELECT DOLT_COMMIT('-Am', 'added 10')",
"SELECT DOLT_CHECKOUT('branch2')",
"INSERT INTO t1 VALUES (20, 21, 22)",
"INSERT INTO t2 VALUES (20, 21, 22)",
"SELECT DOLT_COMMIT('-Am', 'added 20')",
"SELECT DOLT_CHECKOUT('main')",
"SELECT DOLT_MERGE('branch1')",
"SELECT DOLT_MERGE('branch2')",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT * FROM t1",
Expected: []sql.Row{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{10, 11, 12},
{20, 21, 22},
},
},
{
Query: "SELECT * FROM t2",
Expected: []sql.Row{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{10, 11, 12},
{20, 21, 22},
},
},
},
},
{
Name: "merge with check expressions and column defaults",
SetUpScript: []string{
"SET timezone TO 'UTC';",
"CREATE TABLE t1 (a INT, b timestamptz default '2020-01-01 00:00:00'::timestamptz, PRIMARY KEY (a))",
"ALTER TABLE t1 ADD CONSTRAINT check_b CHECK (b >= '2020-01-01 00:00:00'::timestamptz)",
"INSERT INTO t1 VALUES (1, '2020-01-02 00:00:00'), (2, '2020-01-03 00:00:00')",
"SELECT DOLT_COMMIT('-Am', 'intial commit')",
"SELECT DOLT_BRANCH('branch1')",
"SELECT DOLT_BRANCH('branch2')",
"SELECT DOLT_CHECKOUT('branch1')",
"INSERT INTO t1 VALUES (3, '2020-01-04 00:00:00')",
"SELECT DOLT_COMMIT('-Am', 'added 3')",
"SELECT DOLT_CHECKOUT('branch2')",
"INSERT INTO t1 VALUES (4, '2020-01-05 00:00:00')",
"SELECT DOLT_COMMIT('-Am', 'added 4')",
"SELECT DOLT_CHECKOUT('main')",
"SELECT DOLT_MERGE('branch1')",
"SELECT DOLT_MERGE('branch2')",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT * FROM t1 order by a",
Expected: []sql.Row{
{1, "2020-01-02 00:00:00+00"},
{2, "2020-01-03 00:00:00+00"},
{3, "2020-01-04 00:00:00+00"},
{4, "2020-01-05 00:00:00+00"},
},
},
{
// make sure the check constraint is still there
Query: "INSERT INTO t1 VALUES (5, '2019-12-31 00:00:00')",
ExpectedErr: "Check constraint",
},
},
},
{
Name: "merge with unique constraints and foreign keys",
SetUpScript: []string{
"CREATE TABLE t1 (a INT, b INT, PRIMARY KEY (a), unique (b))",
"CREATE TABLE t2 (a INT, b INT, PRIMARY KEY (a), foreign key (b) references t1(b))",
"INSERT INTO t1 VALUES (1, 2), (4, 5), (7, 8)",
"INSERT INTO t2 VALUES (1, 2), (4, 5), (7, 8)",
"SELECT DOLT_COMMIT('-Am', 'intial commit')",
"SELECT DOLT_BRANCH('branch1')",
"SELECT DOLT_BRANCH('branch2')",
"SELECT DOLT_CHECKOUT('branch1')",
"INSERT INTO t1 VALUES (10, 11)",
"INSERT INTO t2 VALUES (10, 11)",
"SELECT DOLT_COMMIT('-Am', 'added 10')",
"SELECT DOLT_CHECKOUT('branch2')",
"INSERT INTO t1 VALUES (20, 21)",
"INSERT INTO t2 VALUES (20, 21)",
"SELECT DOLT_COMMIT('-Am', 'added 20')",
"SELECT DOLT_CHECKOUT('main')",
"SELECT DOLT_MERGE('branch1')",
"SELECT DOLT_MERGE('branch2')",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT * FROM t1 order by a",
Expected: []sql.Row{
{1, 2},
{4, 5},
{7, 8},
{10, 11},
{20, 21},
},
},
{
// make sure the unique constraint is still there
Query: "INSERT INTO t1 VALUES (100, 2)",
ExpectedErr: "unique key",
},
{
// make sure the foreign key constraint is still there
Query: "INSERT INTO t2 VALUES (100, 200)",
ExpectedErr: "Foreign key violation",
},
},
},
})
}
+225
View File
@@ -0,0 +1,225 @@
// Copyright 2026 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"context"
"testing"
"github.com/dolthub/dolt/go/libraries/utils/svcs"
"github.com/dolthub/go-mysql-server/sql"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestMultipleStatements is a test for: https://github.com/dolthub/doltgresql/issues/2175
func TestMultipleStatements(t *testing.T) {
ctx := context.Background()
var conn *Connection
if runOnPostgres {
pgxConn, err := pgx.Connect(ctx, "postgres://postgres:password@127.0.0.1:5432/postgres?sslmode=disable")
require.NoError(t, err)
conn = &Connection{
Default: pgxConn,
Current: pgxConn,
}
require.NoError(t, pgxConn.Ping(ctx))
defer func() {
conn.Close(ctx)
}()
} else {
var controller *svcs.Controller
ctx, conn, controller = CreateServer(t, "postgres")
defer func() {
conn.Close(ctx)
controller.Stop()
err := controller.WaitForStop()
require.NoError(t, err)
}()
}
queries := []string{
`BEGIN;`,
`DROP TABLE IF EXISTS migrations;`,
`DROP TABLE IF EXISTS animals;`,
`CREATE TABLE IF NOT EXISTS migrations (file_name TEXT NOT NULL, file_hash TEXT NOT NULL);`,
`CREATE TABLE IF NOT EXISTS animals (id SERIAL PRIMARY KEY NOT NULL, name TEXT NOT NULL);`,
`;`, // This should be ignored in the output
`INSERT INTO migrations (file_name, file_hash) VALUES ('2021-09-07T154500-create-animals-table.sql', '42331f4277227d09e9bb32eeaf7e04d9c7fe320160e05372ed0ef010cfbf666b');`,
`INSERT INTO animals(name) VALUES('Alpaca');`,
`INSERT INTO animals(name) VALUES('Highland cow');`,
`INSERT INTO animals(name) VALUES('Aardvark');`,
`INSERT INTO migrations (file_name, file_hash) VALUES ('2021-09-07T154700-insert-animals.sql', '3223d0deb6fb7fb2accf6abffc0667ebe4503379987c472d10a585a553f9b3b6');`,
`SELECT * FROM migrations ORDER BY file_name;`,
`SELECT * FROM animals ORDER BY id;`,
`COMMIT;`,
}
combinedQueries := ""
for _, query := range queries {
// We do this just to homogenize the queries, even though we're adding the delimiter right back
query = sql.RemoveSpaceAndDelimiter(query, ';')
combinedQueries += query + ";"
}
// First we'll check all invalid modes that fail immediately
invalidModes := []pgx.QueryExecMode{
pgx.QueryExecModeCacheStatement,
pgx.QueryExecModeCacheDescribe,
pgx.QueryExecModeDescribeExec,
pgx.QueryExecModeExec,
}
for _, mode := range invalidModes {
rows, err := conn.Current.Query(ctx, combinedQueries, mode)
if mode == pgx.QueryExecModeExec {
// This mode requires reading from the returned rows to find the error, rather than erroring immediately
require.NoError(t, err)
_ = rows.Next()
err = rows.Err()
} else {
require.Error(t, err)
}
require.Contains(t, err.Error(), "cannot insert multiple commands into a prepared statement")
}
// Then we'll check the singular valid mode
rows, err := conn.Current.Query(ctx, combinedQueries, pgx.QueryExecModeSimpleProtocol)
require.NoError(t, err)
require.False(t, rows.Next()) // Simple mode doesn't return results with multiple statements
rows.Close()
// Now we'll use the underlying connection to verify all returned results
mrr := conn.Current.PgConn().Exec(ctx, combinedQueries)
results, err := mrr.ReadAll()
require.NoError(t, err)
if assert.Len(t, results, len(testMultipleStatementsResults)) {
for resultIdx, expected := range testMultipleStatementsResults {
result := results[resultIdx]
if assert.Equal(t, len(expected.FieldDescriptions), len(result.FieldDescriptions)) {
for fieldIdx, expectedField := range expected.FieldDescriptions {
resultField := result.FieldDescriptions[fieldIdx]
assert.Equal(t, expectedField.Name, resultField.Name)
assert.Equal(t, expectedField.DataTypeOID, resultField.DataTypeOID)
assert.Equal(t, expectedField.DataTypeSize, resultField.DataTypeSize)
assert.Equal(t, expectedField.TypeModifier, resultField.TypeModifier)
assert.Equal(t, expectedField.Format, resultField.Format)
}
}
if assert.Equal(t, len(expected.Rows), len(result.Rows)) {
for rowIdx, expectedRow := range expected.Rows {
resultRow := result.Rows[rowIdx]
for columnIdx, expectedCol := range expectedRow {
assert.Equal(t, expectedCol, resultRow[columnIdx])
}
}
}
assert.Equal(t, expected.CommandTag, result.CommandTag)
}
}
require.NoError(t, mrr.Close())
// Now we'll ensure that errors are properly handled within multiple statements
queries = []string{
`INSERT INTO animals(name) VALUES('Pigeon');`,
`SELECT * FROM non_existent;`,
`INSERT INTO animals(name) VALUES('Elephant');`,
}
combinedQueries = ""
for _, query := range queries {
query = sql.RemoveSpaceAndDelimiter(query, ';')
combinedQueries += query + ";"
}
mrr = conn.Current.PgConn().Exec(ctx, combinedQueries)
results, err = mrr.ReadAll()
require.Error(t, err)
require.Contains(t, err.Error(), "non_existent")
if assert.Len(t, results, 1) {
assert.Equal(t, results[0].CommandTag, pgconn.NewCommandTag("INSERT 0 1"))
}
}
// testMultipleStatementsResults are used within TestMultipleStatements
var testMultipleStatementsResults = []pgconn.Result{
{CommandTag: pgconn.NewCommandTag("BEGIN")},
{CommandTag: pgconn.NewCommandTag("DROP TABLE")},
{CommandTag: pgconn.NewCommandTag("DROP TABLE")},
{CommandTag: pgconn.NewCommandTag("CREATE TABLE")},
{CommandTag: pgconn.NewCommandTag("CREATE TABLE")},
{CommandTag: pgconn.NewCommandTag("INSERT 0 1")},
{CommandTag: pgconn.NewCommandTag("INSERT 0 1")},
{CommandTag: pgconn.NewCommandTag("INSERT 0 1")},
{CommandTag: pgconn.NewCommandTag("INSERT 0 1")},
{CommandTag: pgconn.NewCommandTag("INSERT 0 1")},
{
FieldDescriptions: []pgconn.FieldDescription{
{
Name: "file_name",
DataTypeOID: 25,
DataTypeSize: -1,
TypeModifier: -1,
Format: 0,
},
{
Name: "file_hash",
DataTypeOID: 25,
DataTypeSize: -1,
TypeModifier: -1,
Format: 0,
},
},
Rows: [][][]byte{
{
[]byte("2021-09-07T154500-create-animals-table.sql"),
[]byte("42331f4277227d09e9bb32eeaf7e04d9c7fe320160e05372ed0ef010cfbf666b"),
},
{
[]byte("2021-09-07T154700-insert-animals.sql"),
[]byte("3223d0deb6fb7fb2accf6abffc0667ebe4503379987c472d10a585a553f9b3b6"),
},
},
CommandTag: pgconn.NewCommandTag("SELECT 2"),
},
{
FieldDescriptions: []pgconn.FieldDescription{
{
Name: "id",
DataTypeOID: 23,
DataTypeSize: 4,
TypeModifier: -1,
Format: 0,
},
{
Name: "name",
DataTypeOID: 25,
DataTypeSize: -1,
TypeModifier: -1,
Format: 0,
},
},
Rows: [][][]byte{
{
[]byte("1"),
[]byte("Alpaca"),
},
{
[]byte("2"),
[]byte("Highland cow"),
},
{
[]byte("3"),
[]byte("Aardvark"),
},
},
CommandTag: pgconn.NewCommandTag("SELECT 3"),
},
{CommandTag: pgconn.NewCommandTag("COMMIT")},
}
File diff suppressed because it is too large Load Diff
+111
View File
@@ -0,0 +1,111 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestParameters(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "default_with_oids",
Assertions: []ScriptTestAssertion{
{
Query: "SELECT default_with_oids;",
Expected: []sql.Row{{0}},
},
{
Query: "SET default_with_oids = false;",
Expected: []sql.Row{},
},
},
},
{
Name: "DateStyle",
Assertions: []ScriptTestAssertion{
{
Query: "SHOW DateStyle;",
Expected: []sql.Row{{"ISO, MDY"}},
},
{
Query: "SELECT timestamp '2001/02/04 04:05:06.789';",
Expected: []sql.Row{{"2001-02-04 04:05:06.789"}},
},
{
Query: "SET datestyle = 'german';",
Expected: []sql.Row{},
},
{
Query: "SHOW DateStyle;",
Expected: []sql.Row{{"German, DMY"}},
},
{
Skip: true, // TODO: the test passes but pgx cannot parse the result
Query: "SELECT timestamp '2001/02/04 04:05:06.789';",
Expected: []sql.Row{{"04.02.2001 04:05:06.789"}},
},
{
Query: "SET datestyle = 'YMD';",
Expected: []sql.Row{},
},
{
Query: "SHOW DateStyle;",
Expected: []sql.Row{{"German, YMD"}},
},
{
Query: "SET datestyle = 'sQl';",
Expected: []sql.Row{},
},
{
Query: "SHOW DateStyle;",
Expected: []sql.Row{{"SQL, YMD"}},
},
{
Skip: true, // TODO: the test passes but pgx cannot parse the result
Query: "SELECT timestamp '2001/02/04 04:05:06.789';",
Expected: []sql.Row{{"02/04/2001 04:05:06.789"}},
},
{
Query: "SET datestyle = 'postgreS';",
Expected: []sql.Row{},
},
{
Query: "SHOW DateStyle;",
Expected: []sql.Row{{"Postgres, YMD"}},
},
{
Skip: true, // TODO: the test passes but pgx cannot parse the result
Query: "SELECT timestamp '2001/02/04 04:05:06.789';",
Expected: []sql.Row{{"Sun Feb 04 04:05:06.789 2001"}},
},
{
Query: "RESET datestyle;",
Expected: []sql.Row{},
},
{
Query: "SHOW DateStyle;",
Expected: []sql.Row{{"ISO, MDY"}},
},
{
Query: "SET datestyle = 'unknown';",
ExpectedErr: `invalid value for parameter "DateStyle": "unknown"`,
},
},
},
})
}
File diff suppressed because it is too large Load Diff
+1581
View File
File diff suppressed because it is too large Load Diff
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestPsqlCommands(t *testing.T) {
RunScripts(t, []ScriptTest{
{
// Many of the psql commands use the OPERATOR(pg_catalog.+) syntax, testing it here directly in a simpler context
Name: "operator keyword",
Assertions: []ScriptTestAssertion{
{
Query: "select 1 OPERATOR(pg_catalog.+) 1",
Expected: []sql.Row{
{2},
},
},
{
Query: "select 1 OPERATOR(PG_CATALOG.+) 1",
Expected: []sql.Row{
{2},
},
},
{
Query: "select 1 OPERATOR(myschema.+) 1",
ExpectedErr: "schema \"myschema\" not allowed",
},
{
Query: "select 1 OPERATOR(pg_catalog.<) 1",
Expected: []sql.Row{
{"f"},
},
},
{
Query: "select 1 OPERATOR(myschema.<) 1",
ExpectedErr: "schema \"myschema\" not allowed",
},
{
Query: "select 1 OPERATOR(pg_catalog.<=) 1",
Expected: []sql.Row{
{"t"},
},
},
{
Query: "select 1 OPERATOR(pg_catalog.=) 1",
Expected: []sql.Row{
{"t"},
},
},
{
Query: "select 'hello' OPERATOR(pg_catalog.~) 'hello';",
Expected: []sql.Row{
{"t"},
},
},
},
},
{
Name: `\dt tablename`,
SetUpScript: []string{
"CREATE TABLE test_table (id INT PRIMARY KEY, name TEXT);",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT n.nspname as \"Schema\", " +
" c.relname as \"Name\", " +
" CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN 'view' WHEN 'm' THEN 'materialized view' WHEN 'i' THEN 'index' WHEN 'S' THEN 'sequence' WHEN 't' THEN 'TOAST table' WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' WHEN 'I' THEN 'partitioned index' END as \"Type\", " +
" pg_catalog.pg_get_userbyid(c.relowner) as \"Owner\" " +
"FROM pg_catalog.pg_class c " +
" LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace " +
" LEFT JOIN pg_catalog.pg_am am ON am.oid = c.relam " +
"WHERE c.relkind IN ('r','p','t','s','') " +
" AND c.relname OPERATOR(pg_catalog.~) '^(test_table)$' COLLATE pg_catalog.default " +
" AND pg_catalog.pg_table_is_visible(c.oid) " +
"ORDER BY 1,2;",
Expected: []sql.Row{{"public", "test_table", "table", "postgres"}},
},
},
},
{
Name: `\d tablename`,
SetUpScript: []string{
"CREATE TABLE test_table (id INT PRIMARY KEY, name TEXT);",
},
Assertions: []ScriptTestAssertion{
{
// these queries return no rows because of the hard-coded oids, should fix
Query: `SELECT pol.polname, pol.polpermissive,
CASE WHEN pol.polroles = '{0}' THEN NULL ELSE pg_catalog.array_to_string(array(select rolname from pg_catalog.pg_roles where oid = any (pol.polroles) order by 1),',') END,
pg_catalog.pg_get_expr(pol.polqual, pol.polrelid),
pg_catalog.pg_get_expr(pol.polwithcheck, pol.polrelid),
CASE pol.polcmd
WHEN 'r' THEN 'SELECT'
WHEN 'a' THEN 'INSERT'
WHEN 'w' THEN 'UPDATE'
WHEN 'd' THEN 'DELETE'
END AS cmd
FROM pg_catalog.pg_policy pol
WHERE pol.polrelid = '4131846889' ORDER BY 1;`,
},
{
Query: `SELECT oid, stxrelid::pg_catalog.regclass,
stxnamespace::pg_catalog.regnamespace::pg_catalog.text AS nsp,
stxname, pg_catalog.pg_get_statisticsobjdef_columns(oid) AS columns,
'd' = any(stxkind) AS ndist_enabled,
'f' = any(stxkind) AS deps_enabled,
'm' = any(stxkind) AS mcv_enabled,
stxstattarget FROM pg_catalog.pg_statistic_ext
WHERE stxrelid = '4131846889' ORDER BY nsp, stxname;`,
},
{
Skip: true, // lots that we don't support yet
Query: `SELECT pubname
, NULL
, NULL
FROM pg_catalog.pg_publication p
JOIN pg_catalog.pg_publication_namespace pn ON p.oid = pn.pnpubid
JOIN pg_catalog.pg_class pc ON pc.relnamespace = pn.pnnspid
WHERE pc.oid ='4131846889' and pg_catalog.pg_relation_is_publishable('4131846889')
UNION
SELECT pubname
, pg_get_expr(pr.prqual, c.oid)
, (CASE WHEN pr.prattrs IS NOT NULL THEN
(SELECT string_agg(attname, ', ')
FROM pg_catalog.generate_series(0, pg_catalog.array_upper(pr.prattrs::pg_catalog.int2[], 1)) s,
pg_catalog.pg_attribute
WHERE attrelid = pr.prrelid AND attnum = prattrs[s])
ELSE NULL END) FROM pg_catalog.pg_publication p
JOIN pg_catalog.pg_publication_rel pr ON p.oid = pr.prpubid
JOIN pg_catalog.pg_class c ON c.oid = pr.prrelid
WHERE pr.prrelid = '4131846889'
UNION
SELECT pubname
, NULL
, NULL
FROM pg_catalog.pg_publication p
WHERE p.puballtables AND pg_catalog.pg_relation_is_publishable('4131846889')
ORDER BY 1;`,
},
},
},
})
}
+391
View File
@@ -0,0 +1,391 @@
// Copyright 2025 Dolthub, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package _go
import (
"testing"
"github.com/dolthub/go-mysql-server/sql"
)
func TestRecords(t *testing.T) {
RunScripts(t, []ScriptTest{
{
Name: "Record cannot be used as column type",
SetUpScript: []string{
"CREATE TABLE t2 (pk INT PRIMARY KEY, c1 VARCHAR(100));",
},
Assertions: []ScriptTestAssertion{
{
Query: "CREATE TABLE t (pk INT PRIMARY KEY, r RECORD);",
ExpectedErr: `column "r" has pseudo-type record`,
},
{
Query: "ALTER TABLE t2 ADD COLUMN c2 RECORD;",
ExpectedErr: `column "c2" has pseudo-type record`,
},
{
Query: "ALTER TABLE t2 ALTER COLUMN c1 TYPE RECORD;",
ExpectedErr: `column "c1" has pseudo-type record`,
},
{
Query: "CREATE DOMAIN my_domain AS record;",
ExpectedErr: `"record" is not a valid base type for a domain`,
},
{
Query: "CREATE SEQUENCE my_seq AS record;",
ExpectedErr: "sequence type must be smallint, integer, or bigint",
},
{
Query: "CREATE TYPE outer_type AS (id int, payload record);",
ExpectedErr: `column "payload" has pseudo-type record`,
},
},
},
{
Name: "Casting to record",
Assertions: []ScriptTestAssertion{
{
Query: "select row(1, 1)::record;",
Expected: []sql.Row{{[]any{1, 1}}},
},
},
},
{
// TODO: Wrapping table rows with ROW() is not supported yet. Planbuilder assumes the
// table alias is a column name and not a table.
Name: "ROW() wrapping table rows",
SetUpScript: []string{
"create table users (name text, location text, age int);",
"insert into users values ('jason', 'SEA', 42), ('max', 'SFO', 31);",
},
Assertions: []ScriptTestAssertion{
{
// TODO: ERROR: column "p" could not be found in any table in scope
Skip: true,
Query: "select row(p) from users p;",
Expected: []sql.Row{{`("(jason,SEA,44)")`}, {`("(max,SFO,31)")`}},
},
{
// TODO: ERROR: name resolution on this statement is not yet supported
Skip: true,
Query: "select row(p.*, 42) from users p;",
Expected: []sql.Row{{`(jason,SEA,42,42)`}, {`(max,SFO,31,42)`}},
},
{
// TODO: ERROR: (E).x is not yet supported
Skip: true,
Query: "SELECT (u).location FROM users u;",
Expected: []sql.Row{{"SEA"}, {"SFO"}},
},
},
},
{
Name: "ROW() wrapping values",
Assertions: []ScriptTestAssertion{
{
Query: "SELECT ROW(1, 2, 3) as myRow;",
Expected: []sql.Row{{[]any{1, 2, 3}}},
},
{
Query: "SELECT (4, 5, 6) as myRow;",
Expected: []sql.Row{{[]any{4, 5, 6}}},
},
{
Query: "SELECT (NULL, 'foo', NULL) as myRow;",
Expected: []sql.Row{{[]any{nil, "foo", nil}}},
},
{
Query: "SELECT (NULL, (1 > 0), 'baz') as myRow;",
Expected: []sql.Row{{[]any{nil, true, "baz"}}},
},
},
},
{
Name: "ROW() equality and comparison",
Assertions: []ScriptTestAssertion{
{
Query: "SELECT ROW(1, 'x') = ROW(1, 'x');",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 'x') = ROW(1, 'y');",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(1, NULL) = ROW(1, 1);",
Expected: []sql.Row{{nil}},
},
{
Query: "SELECT ROW(1, 2) < ROW(1, 3);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 2) < ROW(2, NULL);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(2, 2) < ROW(2, NULL);",
Expected: []sql.Row{{nil}},
},
{
Query: "SELECT ROW(2, 2, 1) < ROW(2, NULL, 2);",
Expected: []sql.Row{{nil}},
},
{
Query: "SELECT ROW(1, 2) < ROW(NULL, 3);",
Expected: []sql.Row{{nil}},
},
{
Query: "SELECT ROW(NULL, NULL, NULL) < ROW(NULL, NULL, NULL);",
Expected: []sql.Row{{nil}},
},
{
Query: "SELECT ROW(1, 2) <= ROW(1, 3);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 2) <= ROW(1, 2);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, NULL) <= ROW(1, 2);",
Expected: []sql.Row{{nil}},
},
{
Query: "SELECT ROW(2, 1) > ROW(1, 999);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(2, 1) > ROW(1, NULL);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(2, 1) >= ROW(1, 999);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(2, 1) >= ROW(2, 1);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(NULL, 1) >= ROW(2, 1);",
Expected: []sql.Row{{nil}},
},
{
Query: "SELECT ROW(1, 2) != ROW(3, 4);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 2) != ROW(NULL, 4);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(NULL, 4) != ROW(NULL, 4);",
Expected: []sql.Row{{nil}},
},
{
// TODO: IS NOT DISTINCT FROM is not yet supported
Skip: true,
Query: "SELECT ROW(1, NULL) IS NOT DISTINCT FROM ROW(1, NULL);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, '2') = ROW(1, 2::TEXT);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 1) = ROW(1, 1);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 1) != ROW(1, 1);",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(1, 1) < ROW(1, 1);",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(1, 1) <= ROW(1, 1);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 1) > ROW(1, 1);",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(1, 1) = ROW(1, 1);",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(1, 2, null, 4) = ROW(null, 2, 3, 5);",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(1, 2, null, 4) != ROW(null, 2, 3, 5);",
Expected: []sql.Row{{"t"}},
},
},
},
{
Name: "ROW() use inserting and selecting composite rows",
SetUpScript: []string{
"CREATE TYPE user_info AS (id INT, name TEXT, email TEXT);",
"CREATE TABLE accounts (info user_info);",
},
Assertions: []ScriptTestAssertion{
{
Query: "INSERT INTO accounts VALUES (ROW(1, 'alice', 'a@example.com'));",
Expected: []sql.Row{},
},
{
Query: "SELECT info FROM accounts;",
Expected: []sql.Row{{"(1,alice,a@example.com)"}},
},
{
Query: "SELECT (a.info).name FROM accounts a;",
Expected: []sql.Row{{"alice"}},
},
},
},
{
Name: "ROW() use in WHERE clause",
SetUpScript: []string{
"create table users (id int primary key, name text, email text);",
"insert into users values (1, 'John', 'j@a.com'), (2, 'Joe', 'joe@joe.com');",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT * FROM users WHERE ROW(id, name, email) = ROW(1, 'John', 'j@a.com');",
Expected: []sql.Row{{1, "John", "j@a.com"}},
},
{
// TODO: IS NOT DISTINCT FROM is not yet supported
Skip: true,
Query: "SELECT * FROM users WHERE ROW(id, name) IS NOT DISTINCT FROM ROW(2, 'Jane');",
Expected: []sql.Row{{2, "Joe", "joe@joe.com"}},
},
},
},
{
Name: "ROW() casting and type inference",
Assertions: []ScriptTestAssertion{
{
Query: "SELECT ROW(1, 'a')::record;",
Expected: []sql.Row{{[]any{1, "a"}}},
},
{
Query: "SELECT ROW(1, 2) = ROW(1, 'two');",
ExpectedErr: "invalid input syntax",
},
{
Query: "SELECT ROW(1, 2) = ROW(1, '2');",
Expected: []sql.Row{{"t"}},
},
},
},
{
Name: "ROW() error cases and edge conditions",
SetUpScript: []string{
"create table users (id int primary key, name text, email text);",
"insert into users values (1, 'John', 'j@a.com'), (2, 'Joe', 'joe@joe.com');",
},
Assertions: []ScriptTestAssertion{
{
Query: "SELECT ROW(1, 2) = ROW(1);",
ExpectedErr: "unequal number of entries",
},
{
Query: "SELECT ROW(1, 2) = ROW(1, 2, 3);",
ExpectedErr: "unequal number of entries",
},
{
Query: "SELECT ROW(1, 2) < ROW(1);",
ExpectedErr: "unequal number of entries",
},
{
Query: "SELECT ROW(1, 2) <= ROW(1);",
ExpectedErr: "unequal number of entries",
},
{
Query: "SELECT ROW(1, 2) > ROW(1);",
ExpectedErr: "unequal number of entries",
},
{
Query: "SELECT ROW(1, 2) >= ROW(1);",
ExpectedErr: "unequal number of entries",
},
{
Query: "SELECT ROW(1, 2) != ROW(1);",
ExpectedErr: "unequal number of entries",
},
{
Query: "SELECT NULL::record IS NULL",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(NULL) IS NULL",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(NULL, NULL, NULL) IS NULL;",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(NULL, 42, NULL) IS NULL;",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(42) IS NULL",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(NULL) IS NOT NULL;",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(NULL, NULL) IS NOT NULL;",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(NULL, 1) IS NOT NULL;",
Expected: []sql.Row{{"f"}},
},
{
Query: "SELECT ROW(1, 1) IS NOT NULL;",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(42) IS NOT NULL;",
Expected: []sql.Row{{"t"}},
},
{
Query: "SELECT ROW(id, name), COUNT(*) FROM users GROUP BY ROW(id, name);",
Expected: []sql.Row{{[]any{1, "John"}, 1}, {[]any{2, "Joe"}, 1}},
},
},
},
{
Name: "ROW() nesting",
Assertions: []ScriptTestAssertion{
{
Query: "SELECT ROW(ROW(1, 'x'), true);",
Expected: []sql.Row{{[]any{[]any{1, "x"}, true}}},
},
},
},
})
}
+4
View File
@@ -0,0 +1,4 @@
56 7.8
100 99.097
0 0.09561
42 324.78
+103
View File
@@ -0,0 +1,103 @@
1 {92,75,71,52,64,83} {AAAAAAAA44066,AAAAAA1059,AAAAAAAAAAA176,AAAAAAA48038}
2 {3,6} {AAAAAA98232,AAAAAAAA79710,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAAAAAAA55798,AAAAAAAAA12793}
3 {37,64,95,43,3,41,13,30,11,43} {AAAAAAAAAA48845,AAAAA75968,AAAAA95309,AAA54451,AAAAAAAAAA22292,AAAAAAA99836,A96617,AA17009,AAAAAAAAAAAAAA95246}
4 {71,39,99,55,33,75,45} {AAAAAAAAA53663,AAAAAAAAAAAAAAA67062,AAAAAAAAAA64777,AAA99043,AAAAAAAAAAAAAAAAAAA91804,39557}
5 {50,42,77,50,4} {AAAAAAAAAAAAAAAAA26540,AAAAAAA79710,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA176,AAAAA95309,AAAAAAAAAAA46154,AAAAAA66777,AAAAAAAAA27249,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA70104}
6 {39,35,5,94,17,92,60,32} {AAAAAAAAAAAAAAA35875,AAAAAAAAAAAAAAAA23657}
7 {12,51,88,64,8} {AAAAAAAAAAAAAAAAAA12591,AAAAAAAAAAAAAAAAA50407,AAAAAAAAAAAA67946}
8 {60,84} {AAAAAAA81898,AAAAAA1059,AAAAAAAAAAAA81511,AAAAA961,AAAAAAAAAAAAAAAA31334,AAAAA64741,AA6416,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAA50407}
9 {56,52,35,27,80,44,81,22} {AAAAAAAAAAAAAAA73034,AAAAAAAAAAAAA7929,AAAAAAA66161,AA88409,39557,A27153,AAAAAAAA9523,AAAAAAAAAAA99000}
10 {71,5,45} {AAAAAAAAAAA21658,AAAAAAAAAAAA21089,AAA54451,AAAAAAAAAAAAAAAAAA54141,AAAAAAAAAAAAAA28620,AAAAAAAAAAA21658,AAAAAAAAAAA74076,AAAAAAAAA27249}
11 {41,86,74,48,22,74,47,50} {AAAAAAAA9523,AAAAAAAAAAAA37562,AAAAAAAAAAAAAAAA14047,AAAAAAAAAAA46154,AAAA41702,AAAAAAAAAAAAAAAAA764,AAAAA62737,39557}
12 {17,99,18,52,91,72,0,43,96,23} {AAAAA33250,AAAAAAAAAAAAAAAAAAA85420,AAAAAAAAAAA33576}
13 {3,52,34,23} {AAAAAA98232,AAAA49534,AAAAAAAAAAA21658}
14 {78,57,19} {AAAA8857,AAAAAAAAAAAAAAA73034,AAAAAAAA81587,AAAAAAAAAAAAAAA68526,AAAAA75968,AAAAAAAAAAAAAA65909,AAAAAAAAA10012,AAAAAAAAAAAAAA65909}
15 {17,14,16,63,67} {AA6416,AAAAAAAAAA646,AAAAA95309}
16 {14,63,85,11} {AAAAAA66777}
17 {7,10,81,85} {AAAAAA43678,AAAAAAA12144,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAAAAA15356}
18 {1} {AAAAAAAAAAA33576,AAAAA95309,64261,AAA59323,AAAAAAAAAAAAAA95246,55847,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAAAA64374}
19 {52,82,17,74,23,46,69,51,75} {AAAAAAAAAAAAA73084,AAAAA75968,AAAAAAAAAAAAAAAA14047,AAAAAAA80240,AAAAAAAAAAAAAAAAAAA1205,A68938}
20 {72,89,70,51,54,37,8,49,79} {AAAAAA58494}
21 {2,8,65,10,5,79,43} {AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAAAAA91804,AAAAA64669,AAAAAAAAAAAAAAAA1443,AAAAAAAAAAAAAAAA23657,AAAAA12179,AAAAAAAAAAAAAAAAA88852,AAAAAAAAAAAAAAAA31334,AAAAAAAAAAAAAAAA41303,AAAAAAAAAAAAAAAAAAA85420}
22 {11,6,56,62,53,30} {AAAAAAAA72908}
23 {40,90,5,38,72,40,30,10,43,55} {A6053,AAAAAAAAAAA6119,AA44673,AAAAAAAAAAAAAAAAA764,AA17009,AAAAA17383,AAAAA70514,AAAAA33250,AAAAA95309,AAAAAAAAAAAA37562}
24 {94,61,99,35,48} {AAAAAAAAAAA50956,AAAAAAAAAAA15165,AAAA85070,AAAAAAAAAAAAAAA36627,AAAAA961,AAAAAAAAAA55219}
25 {31,1,10,11,27,79,38} {AAAAAAAAAAAAAAAAAA59334,45449}
26 {71,10,9,69,75} {47735,AAAAAAA21462,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA91804,AAAAAAAAA72121,AAAAAAAAAAAAAAAAAAA1205,AAAAA41597,AAAA8857,AAAAAAAAAAAAAAAAAAA15356,AA17009}
27 {94} {AA6416,A6053,AAAAAAA21462,AAAAAAA57334,AAAAAAAAAAAAAAAAAA12591,AA88409,AAAAAAAAAAAAA70254}
28 {14,33,6,34,14} {AAAAAAAAAAAAAAA13198,AAAAAAAA69452,AAAAAAAAAAA82945,AAAAAAA12144,AAAAAAAAA72121,AAAAAAAAAA18601}
29 {39,21} {AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAAAAA38885,AAAA85070,AAAAAAAAAAAAAAAAAAA70104,AAAAA66674,AAAAAAAAAAAAA62007,AAAAAAAA69452,AAAAAAA1242,AAAAAAAAAAAAAAAA1729,AAAA35194}
30 {26,81,47,91,34} {AAAAAAAAAAAAAAAAAAA70104,AAAAAAA80240}
31 {80,24,18,21,54} {AAAAAAAAAAAAAAA13198,AAAAAAAAAAAAAAAAAAA70415,A27153,AAAAAAAAA53663,AAAAAAAAAAAAAAAAA50407,A68938}
32 {58,79,82,80,67,75,98,10,41} {AAAAAAAAAAAAAAAAAA61286,AAA54451,AAAAAAAAAAAAAAAAAAA87527,A96617,51533}
33 {74,73} {A85417,AAAAAAA56483,AAAAA17383,AAAAAAAAAAAAA62159,AAAAAAAAAAAA52814,AAAAAAAAAAAAA85723,AAAAAAAAAAAAAAAAAA55796}
34 {70,45} {AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAA28620,AAAAAAAAAA55219,AAAAAAAA23648,AAAAAAAAAA22292,AAAAAAA1242}
35 {23,40} {AAAAAAAAAAAA52814,AAAA48949,AAAAAAAAA34727,AAAA8857,AAAAAAAAAAAAAAAAAAA62179,AAAAAAAAAAAAAAA68526,AAAAAAA99836,AAAAAAAA50094,AAAA91194,AAAAAAAAAAAAA73084}
36 {79,82,14,52,30,5,79} {AAAAAAAAA53663,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA89194,AA88409,AAAAAAAAAAAAAAA81326,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAA33598}
37 {53,11,81,39,3,78,58,64,74} {AAAAAAAAAAAAAAAAAAA17075,AAAAAAA66161,AAAAAAAA23648,AAAAAAAAAAAAAA10611}
38 {59,5,4,95,28} {AAAAAAAAAAA82945,A96617,47735,AAAAA12179,AAAAA64669,AAAAAA99807,AA74433,AAAAAAAAAAAAAAAAA59387}
39 {82,43,99,16,74} {AAAAAAAAAAAAAAA67062,AAAAAAA57334,AAAAAAAAAAAAAA65909,A27153,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAA64777,AAAAAAAAAAAA81511,AAAAAAAAAAAAAA65909,AAAAAAAAAAAAAA28620}
40 {34} {AAAAAAAAAAAAAA10611,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAA50956,AAAAAAAAAAAAAAAA31334,AAAAA70466,AAAAAAAA81587,AAAAAAA74623}
41 {19,26,63,12,93,73,27,94} {AAAAAAA79710,AAAAAAAAAA55219,AAAA41702,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA71621,AAAAAAAAAAAAAAAAA63050,AAAAAAA99836,AAAAAAAAAAAAAA8666}
42 {15,76,82,75,8,91} {AAAAAAAAAAA176,AAAAAA38063,45449,AAAAAA54032,AAAAAAA81898,AA6416,AAAAAAAAAAAAAAAAAAA62179,45449,AAAAA60038,AAAAAAAA81587}
43 {39,87,91,97,79,28} {AAAAAAAAAAA74076,A96617,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAAAAA55796,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAA67946}
44 {40,58,68,29,54} {AAAAAAA81898,AAAAAA66777,AAAAAA98232}
45 {99,45} {AAAAAAAA72908,AAAAAAAAAAAAAAAAAAA17075,AA88409,AAAAAAAAAAAAAAAAAA36842,AAAAAAA48038,AAAAAAAAAAAAAA10611}
46 {53,24} {AAAAAAAAAAA53908,AAAAAA54032,AAAAA17383,AAAA48949,AAAAAAAAAA18601,AAAAA64669,45449,AAAAAAAAAAA98051,AAAAAAAAAAAAAAAAAA71621}
47 {98,23,64,12,75,61} {AAA59323,AAAAA95309,AAAAAAAAAAAAAAAA31334,AAAAAAAAA27249,AAAAA17383,AAAAAAAAAAAA37562,AAAAAA1059,A84822,55847,AAAAA70466}
48 {76,14} {AAAAAAAAAAAAA59671,AAAAAAAAAAAAAAAAAAA91804,AAAAAA66777,AAAAAAAAAAAAAAAAAAA89194,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAA73084,AAAAAAA79710,AAAAAAAAAAAAAAA40402,AAAAAAAAAAAAAAAAAAA65037}
49 {56,5,54,37,49} {AA21643,AAAAAAAAAAA92631,AAAAAAAA81587}
50 {20,12,37,64,93} {AAAAAAAAAA5483,AAAAAAAAAAAAAAAAAAA1205,AA6416,AAAAAAAAAAAAAAAAA63050,AAAAAAAAAAAAAAAAAA47955}
51 {47} {AAAAAAAAAAAAAA96505,AAAAAAAAAAAAAAAAAA36842,AAAAA95309,AAAAAAAA81587,AA6416,AAAA91194,AAAAAA58494,AAAAAA1059,AAAAAAAA69452}
52 {89,0} {AAAAAAAAAAAAAAAAAA47955,AAAAAAA48038,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAA73084,AAAAA70466,AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA46154,AA66862}
53 {38,17} {AAAAAAAAAAA21658}
54 {70,47} {AAAAAAAAAAAAAAAAAA54141,AAAAA40681,AAAAAAA48038,AAAAAAAAAAAAAAAA29150,AAAAA41597,AAAAAAAAAAAAAAAAAA59334,AA15322}
55 {47,79,47,64,72,25,71,24,93} {AAAAAAAAAAAAAAAAAA55796,AAAAA62737}
56 {33,7,60,54,93,90,77,85,39} {AAAAAAAAAAAAAAAAAA32918,AA42406}
57 {23,45,10,42,36,21,9,96} {AAAAAAAAAAAAAAAAAAA70415}
58 {92} {AAAAAAAAAAAAAAAA98414,AAAAAAAA23648,AAAAAAAAAAAAAAAAAA55796,AA25381,AAAAAAAAAAA6119}
59 {9,69,46,77} {39557,AAAAAAA89932,AAAAAAAAAAAAAAAAA43052,AAAAAAAAAAAAAAAAA26540,AAA20874,AA6416,AAAAAAAAAAAAAAAAAA47955}
60 {62,2,59,38,89} {AAAAAAA89932,AAAAAAAAAAAAAAAAAAA15356,AA99927,AA17009,AAAAAAAAAAAAAAA35875}
61 {72,2,44,95,54,54,13} {AAAAAAAAAAAAAAAAAAA91804}
62 {83,72,29,73} {AAAAAAAAAAAAA15097,AAAA8857,AAAAAAAAAAAA35809,AAAAAAAAAAAA52814,AAAAAAAAAAAAAAAAAAA38885,AAAAAAAAAAAAAAAAAA24183,AAAAAA43678,A96617}
63 {11,4,61,87} {AAAAAAAAA27249,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAA13198,AAA20874,39557,51533,AAAAAAAAAAA53908,AAAAAAAAAAAAAA96505,AAAAAAAA78938}
64 {26,19,34,24,81,78} {A96617,AAAAAAAAAAAAAAAAAAA70104,A68938,AAAAAAAAAAA53908,AAAAAAAAAAAAAAA453,AA17009,AAAAAAA80240}
65 {61,5,76,59,17} {AAAAAA99807,AAAAA64741,AAAAAAAAAAA53908,AA21643,AAAAAAAAA10012}
66 {31,23,70,52,4,33,48,25} {AAAAAAAAAAAAAAAAA69675,AAAAAAAA50094,AAAAAAAAAAA92631,AAAA35194,39557,AAAAAAA99836}
67 {31,94,7,10} {AAAAAA38063,A96617,AAAA35194,AAAAAAAAAAAA67946}
68 {90,43,38} {AA75092,AAAAAAAAAAAAAAAAA69675,AAAAAAAAAAA92631,AAAAAAAAA10012,AAAAAAAAAAAAA7929,AA21643}
69 {67,35,99,85,72,86,44} {AAAAAAAAAAAAAAAAAAA1205,AAAAAAAA50094,AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAAAAAAA47955}
70 {56,70,83} {AAAA41702,AAAAAAAAAAA82945,AA21643,AAAAAAAAAAA99000,A27153,AA25381,AAAAAAAAAAAAAA96505,AAAAAAA1242}
71 {74,26} {AAAAAAAAAAA50956,AA74433,AAAAAAA21462,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAA36627,AAAAAAAAAAAAA70254,AAAAAAAAAA43419,39557}
72 {22,1,16,78,20,91,83} {47735,AAAAAAA56483,AAAAAAAAAAAAA93788,AA42406,AAAAAAAAAAAAA73084,AAAAAAAA72908,AAAAAAAAAAAAAAAAAA61286,AAAAA66674,AAAAAAAAAAAAAAAAA50407}
73 {88,25,96,78,65,15,29,19} {AAA54451,AAAAAAAAA27249,AAAAAAA9228,AAAAAAAAAAAAAAA67062,AAAAAAAAAAAAAAAAAAA70415,AAAAA17383,AAAAAAAAAAAAAAAA33598}
74 {32} {AAAAAAAAAAAAAAAA1729,AAAAAAAAAAAAA22860,AAAAAA99807,AAAAA17383,AAAAAAAAAAAAAAA67062,AAAAAAAAAAA15165,AAAAAAAAAAA50956}
75 {12,96,83,24,71,89,55} {AAAA48949,AAAAAAAA29716,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA67946,AAAAAAAAAAAAAAAA29150,AAA28075,AAAAAAAAAAAAAAAAA43052}
76 {92,55,10,7} {AAAAAAAAAAAAAAA67062}
77 {97,15,32,17,55,59,18,37,50,39} {AAAAAAAAAAAA67946,AAAAAA54032,AAAAAAAA81587,55847,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAAAAA43052,AAAAAA75463,AAAA49534,AAAAAAAA44066}
78 {55,89,44,84,34} {AAAAAAAAAAA6119,AAAAAAAAAAAAAA8666,AA99927,AA42406,AAAAAAA81898,AAAAAAA9228,AAAAAAAAAAA92631,AA21643,AAAAAAAAAAAAAA28620}
79 {45} {AAAAAAAAAA646,AAAAAAAAAAAAAAAAAAA70415,AAAAAA43678,AAAAAAAA72908}
80 {74,89,44,80,0} {AAAA35194,AAAAAAAA79710,AAA20874,AAAAAAAAAAAAAAAAAAA70104,AAAAAAAAAAAAA73084,AAAAAAA57334,AAAAAAA9228,AAAAAAAAAAAAA62007}
81 {63,77,54,48,61,53,97} {AAAAAAAAAAAAAAA81326,AAAAAAAAAA22292,AA25381,AAAAAAAAAAA74076,AAAAAAA81898,AAAAAAAAA72121}
82 {34,60,4,79,78,16,86,89,42,50} {AAAAA40681,AAAAAAAAAAAAAAAAAA12591,AAAAAAA80240,AAAAAAAAAAAAAAAA55798,AAAAAAAAAAAAAAAAAAA70104}
83 {14,10} {AAAAAAAAAA22292,AAAAAAAAAAAAA70254,AAAAAAAAAAA6119}
84 {11,83,35,13,96,94} {AAAAA95309,AAAAAAAAAAAAAAAAAA32918,AAAAAAAAAAAAAAAAAA24183}
85 {39,60} {AAAAAAAAAAAAAAAA55798,AAAAAAAAAA22292,AAAAAAA66161,AAAAAAA21462,AAAAAAAAAAAAAAAAAA12591,55847,AAAAAA98232,AAAAAAAAAAA46154}
86 {33,81,72,74,45,36,82} {AAAAAAAA81587,AAAAAAAAAAAAAA96505,45449,AAAA80176}
87 {57,27,50,12,97,68} {AAAAAAAAAAAAAAAAA26540,AAAAAAAAA10012,AAAAAAAAAAAA35809,AAAAAAAAAAAAAAAA29150,AAAAAAAAAAA82945,AAAAAA66777,31228,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAA28620,AAAAAAAAAAAAAA96505}
88 {41,90,77,24,6,24} {AAAA35194,AAAA35194,AAAAAAA80240,AAAAAAAAAAA46154,AAAAAA58494,AAAAAAAAAAAAAAAAAAA17075,AAAAAAAAAAAAAAAAAA59334,AAAAAAAAAAAAAAAAAAA91804,AA74433}
89 {40,32,17,6,30,88} {AA44673,AAAAAAAAAAA6119,AAAAAAAAAAAAAAAA23657,AAAAAAAAAAAAAAAAAA47955,AAAAAAAAAAAAAAAA33598,AAAAAAAAAAA33576,AA44673}
90 {88,75} {AAAAA60038,AAAAAAAA23648,AAAAAAAAAAA99000,AAAA41702,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAA68526}
91 {78} {AAAAAAAAAAAAA62007,AAA99043}
92 {85,63,49,45} {AAAAAAA89932,AAAAAAAAAAAAA22860,AAAAAAAAAAAAAAAAAAA1205,AAAAAAAAAAAA21089}
93 {11} {AAAAAAAAAAA176,AAAAAAAAAAAAAA8666,AAAAAAAAAAAAAAA453,AAAAAAAAAAAAA85723,A68938,AAAAAAAAAAAAA9821,AAAAAAA48038,AAAAAAAAAAAAAAAAA59387,AA99927,AAAAA17383}
94 {98,9,85,62,88,91,60,61,38,86} {AAAAAAAA81587,AAAAA17383,AAAAAAAA81587}
95 {47,77} {AAAAAAAAAAAAAAAAA764,AAAAAAAAAAA74076,AAAAAAAAAA18107,AAAAA40681,AAAAAAAAAAAAAAA35875,AAAAA60038,AAAAAAA56483}
96 {23,97,43} {AAAAAAAAAA646,A87088}
97 {54,2,86,65} {47735,AAAAAAA99836,AAAAAAAAAAAAAAAAA6897,AAAAAAAAAAAAAAAA29150,AAAAAAA80240,AAAAAAAAAAAAAAAA98414,AAAAAAA56483,AAAAAAAAAAAAAAAA29150,AAAAAAA39692,AA21643}
98 {38,34,32,89} {AAAAAAAAAAAAAAAAAA71621,AAAA8857,AAAAAAAAAAAAAAAAAAA65037,AAAAAAAAAAAAAAAA31334,AAAAAAAAAA48845}
99 {37,86} {AAAAAAAAAAAAAAAAAA32918,AAAAA70514,AAAAAAAAA10012,AAAAAAAAAAAAAAAAA59387,AAAAAAAAAA64777,AAAAAAAAAAAAAAAAAAA15356}
100 {85,32,57,39,49,84,32,3,30} {AAAAAAA80240,AAAAAAAAAAAAAAAA1729,AAAAA60038,AAAAAAAAAAA92631,AAAAAAAA9523}
101 {} {}
102 {NULL} {NULL}
103 \N \N
+2
View File
@@ -0,0 +1,2 @@
5 !check failed 6
7 check failed 6
+2
View File
@@ -0,0 +1,2 @@
4 !check failed 5
6 OK 4
+2
View File
@@ -0,0 +1,2 @@
toy sharon
shoe bob
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
sharon 25 (15,12) 1000 sam
sam 30 (10,5) 2000 bill
bill 20 (11,10) 1000 sharon
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
mike 40 (3.1,6.2)
joe 20 (5.5,2.5)
sally 34 (3.8,45.8)
sandra 19 (9.345,09.6)
alex 30 (1.352,8.2)
sue 50 (8.34,7.375)
denise 24 (3.78,87.90)
sarah 88 (8.4,2.3)
teresa 38 (7.7,1.8)
nan 28 (6.35,0.43)
leah 68 (0.6,3.37)
wendy 78 (2.62,03.3)
melissa 28 (3.089,087.23)
joan 18 (9.4,47.04)
mary 08 (3.7,39.20)
jane 58 (1.34,0.44)
liza 38 (9.76,6.90)
jean 28 (8.561,7.3)
jenifer 38 (6.6,23.3)
juanita 58 (4.57,35.8)
susan 78 (6.579,3)
zena 98 (0.35,0)
martie 88 (8.358,.93)
chris 78 (9.78,2)
pat 18 (1.19,0.6)
zola 58 (2.56,4.3)
louise 98 (5.0,8.7)
edna 18 (1.53,3.5)
bertha 88 (2.75,9.4)
sumi 38 (1.15,0.6)
koko 88 (1.7,5.5)
gina 18 (9.82,7.5)
rean 48 (8.5,5.0)
sharon 78 (9.237,8.8)
paula 68 (0.5,0.5)
julie 68 (3.6,7.2)
belinda 38 (8.9,1.7)
karen 48 (8.73,0.0)
carina 58 (4.27,8.8)
diane 18 (5.912,5.3)
esther 98 (5.36,7.6)
trudy 88 (6.01,0.5)
fanny 08 (1.2,0.9)
carmen 78 (3.8,8.2)
lita 25 (1.3,8.7)
pamela 48 (8.21,9.3)
sandy 38 (3.8,0.2)
trisha 88 (1.29,2.2)
uma 78 (9.73,6.4)
velma 68 (8.8,8.9)
@@ -0,0 +1,5 @@
0 Oakland ((-122,37.9),(-121.7,37.9),(-121.7,37.4),(-122,37.4))
0 Oakland ((-121.7,37.4),(-121.7,37),(-122.1,37),(-122.1,37.3),(-122,37.3),(-122,37.4))
0 Oakland ((-122.1,37.3),(-122.2,37.5),(-122,37.5))
0 Berkeley ((-122.3,37.9),(-122,37.9),(-122,37.6),(-122.3,37.6))
0 Lafayette ((-122.3,37.4),(-122.2,37.4),(-122.2,37),(-122.3,37))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3
View File
@@ -0,0 +1,3 @@
jeff 23 (8,7.7) 600 sharon 3.50000000000000000e+00 \N
cim 30 (10.5,4.7) 400 \N 3.39999999999999990e+00 \N
linda 19 (0.9,6.1) 100 \N 2.89999999999999990e+00 \N
+2
View File
@@ -0,0 +1,2 @@
fred 28 (3.1,-1.5) 3.70000000000000020e+00
larry 60 (21.8,4.9) 3.10000000000000010e+00
File diff suppressed because it is too large Load Diff
+508
View File
@@ -0,0 +1,508 @@
\n
\n
\n
\n
\n
\n
\n
\n
\n i8 hy qo xa jl wr le l5 ja jx zf ro vw wd wa cc mm wh fn yd td l8 ec rv th oc ix ir sm y4 gh pr qg ue cx ww zv c9 zv tx eo f5 gd km b9 wb rm ym yl xj u7 xz uk iq tm ux di if uc hc ge
\n gr ty ph jh po wa iw ag wq r3 yd ow rb ip et ej yl a9 dk pu y6 su ov hf xe qe sd qr zt kp ml ea tp pg dq e3 s3 hh gn hz j7 hb qs qd v0 v4 w0 nu ee wk ez un rd sz wx e7 pn yf gh uh ki kx rb qv f1 bh sr yj ry r2
\n q1 q8 wp w9 vs ww rq de qt wo qp sa rv mc sn u8 yl
\n hv ra sa fr qs ps 4w z5 ls wt ad wy q6 zg bd vt wa e4 ft w7 ld es yg et ic pm sw ja qv ov jm ma b3 wu wi qy ug hs wh ex rt tj en ur e2 ut gv as ui dy qy du qo gv cy lx kw xm fl x2 hd ny nu hh dt wg wh rs wb wz yy yu tj ha ak rw sw io h1 ux ku v6 wc qa rv xb s8 qd f2 zo k2 ew w4 yh yu yi
\n rs tt gp qh wt q6 lg zh vr b8 uy uu lh px jm ww qe xu fp fd rs qu ki dr fn gq gw jv oq zt 2r lc ke wg l9 x3 x5 7g vs ar e7 u2 s8 t0 av dj kl nm u2 zp gf yw ee oc tw a1
\n qs uz wr gq q9 rl e0 pe dj a9 hp qw aw er kq pp uu pl zo wp fr r6 ej pv u5 hh av lw ko qc pn qj ez n8 wn eu tq
\n po h9 rd qs hr la u0 um me wp 0p rl mv rc ab r0 fe fj fk qn jh iy cn lb bl ln b5 ll yg yh qt qp uz od dq as gn cr qa wa cu fy zy vo xk eq vg mr ns yy t7 yi op th yo ov pv em tc hg az io s5 ct os wu lq dr mp hk si gx
\n hm k5 pw a5 qh nb q3 ql wr wt z7 oz wu wh kv q8 c3 mt mg hb a3 rz pz uo y1 rb av us ek dz q0 d3 qw j2 ls wy qq jf ng eo gl ed ix em he qt du hp jc f2 m9 qp hb l4 gy zf l6 qr dn cp x1 oh qk kk s3 hy wg zs ot wj sl oz ie e9 ay it u5 ai hm gh py hz qk ki h8 ja zu qb ei vc qj hg ev h6 yh u0 tb id
\n qg d1 bt c5 r3 iv g6 d7 rc ml gk uh yn y0 zo uh qd wh ib uo u4 om qg ql yz
\n hb a3 q5 pl yj lo qy ki sy fo rj kk zq dl wn 7a zi wn wm yr w3 tv r1
\n ft k6 iz qn qj q2 q3 bl zd av ro wo lk tg ea ew ed y1 ia yl ic g6 po aw sc zm qn gl wq qw zr jp wt j5 gs vt qt yc rr op yw tl ye hr i8 tb uu j0 xd lz vu nl qd fu wg pf wj bt ee wh t2 tp sz um oo tg ha u4 f5 sw pq pr ju qk mh ki zb vj ob cx df hj ef cj q6 u9 tv rv o4 sy ru fq ir
\n ps ko uk tz vv um t9 uk k2 ja o6 ob
\n qs nb gh ld q7 jc sp el w0 py qx i2 qe la rl qw tu ti dq ue iv oi wa qr ed t3 fg oa of rr fv qz xn wu wq te hx
\n yb ty pq az fi qg qn la bu ji lg wg q8 mi cv rl up lg om oq ym pv in aq gg js ha on ww qr bj vn pv he b5 mh qe cc mk qt rb eu qy rw tr qo ec op sn oh e2 ao iv e4 hy dt s6 qt p1 hb ih qs wg x1 bd l1 t1 ro r9 uv wb aw gu os t0 ah e0 s0 hj pe or qj zz ql fd ks qv bq qm bg ec ry oj u8 u0 yj ru r1 yx o7
\n z4 wr qz cg nq ir bb gb w7 e5 zc pj e9 px uo fp ts aq db q9 iy qe zv xu a9 l1 mb qw tc qu fi hw ur de e4 hk lj wo wf fi ep rl wh vh ek vp oi sv rh ay hj px aa er tv do ir
\n tr o9 gb tt pp qa qs a5 ps rf q1 kj by ub ru ox co o8 ny wp wa ws rd kk b1 zc rl rz uo ts ig fh db qm q0 bg rr fu ld lr wb en nd cw vr hy rn qr en em au p8 so oh ut hz gq wp ow be ky wj dw t1 pl er wc ot na r9 wl ou un um wx iq sc e8 sn re rr f7 hz h4 ce wz qx wx kp px tl tx ai wq hf ec 6u rz og yt ok yy yp
\n sa pp a7 qm qh of je qj lo ph wt h0 ji cg z8 2v xs zl mo ik hm on tu d8 av ot pn iv ez ja qn pq wy 7r mq qu p1 tu p6 ti ur pj uy ui qo i9 qa nj xm s1 ya fb 7j ro wn t6 wz yu iq yi go en pb aj f5 hf ug uh hk av pr wl wz im ja v9 u2 ks it br wv wn se ia o5 ox ei r2 ig aj sp
\n sa tn z8 ew uo eh g8 zt wy 27 ff uh te en pd eh hv 2e wh ty oi sw xx 2p qs mx wb q3 rl eq aa eu
\n d4 ef ta zq j2 em c0 vv wf kj dw uk ql y9 rn
\n sq nm kl w8 ur kz c1 pc y1 g4 oi jv wr zy ew by se ec yn ti gq gt rd l5 ej yp tk da qz qx ir wm on q2 to ew
\n rd gu z2 kj qk bl 6d wy nw xq iu 8t ri uc kq nx ql oa vi kd o6
\n ra gr he wy q0 ow ti ia pb ha qr lv ms qu pu qw qr ml qt ep sv i5 of fm oe nl xh x1 xz u4 ha ao fc ug pw nh n9 qv kh vx uq w1 u0 ei if
\n q1 d2 qz zd jd qb wj nt ah mj ea ed y1 et fj qe en b8 ty iv ht fv tn tm sg jb ky ai en us tl ud iu zj ql u1 ci ru iw tw
\n fr ub h9 pd ub jk vh z6 wu wh wp 5z yt w9 w0 uy om tl rc r6 ax d7 et y2 tw dz se vf ii m3 lf b4 jf vr qw qy uf es qp en tl to ye ue ph e3 uy i0 jl pz oe qo zp wp ft ka zf qd wd kr qf l9 mm wf qx ef t3 x8 ex rg ev s8 ys it da rw al hn tc f6 fv nd nc ad fj nr x0 bx yq ti rx ok tb hx o8 dp
\n o0 jq un xu q8 wo qq gg ta oj ec az dl bl wb
\n o9 ij pq gu gp nv qk gg la q4 nw bo z8 9a iw wu q8 eh wi nt jk ut ys c1 r5 up y1 yl py oy ht gd td db qn cz qw lp re c7 dh j5 ia bz dj qr qt wd wf qi rt sv ul uz tl ta yr e4 tm sg pc jv hc hv lc xg xm br vf r8 na wl ou td wc up rj s8 e8 ir ys ii qk p0 lt ho wb x8 bv lw w1 rz ew aa rv ry gx o8
\n tt hn gn un db fu uq qf d4 q3 pp ji lf wu bx q8 hx kb ny t5 bn hb ex yf ef yj g1 g2 to yk g3 ej sk hy dv qc gj qv sy bg wr na wy bx z0 rc rm ml ug te qp i5 ue oj s4 im oq qt gx sa gt l4 sv at v3 bq mv wd x3 80 x8 aq xk rg yp en gs us dq ak tz al tx o2 dg f9 kv or h4 jy k1 jo h8 kp lt os kh as tn eu ul tm su an tw sp
\n za yi pe sh pv y4 y5 hy th jg qy qt ke ti ue qk yy ie cq wl p0 lw mf er w5
\n k9 bt xu kc me is o5 z9 kb gv ur rc oe sk qn ve wi mm rn eu to ue uy qa xf by t1 td t7 aw up yf pr dk cg zr sc 3d at rw ec rl st zo rn do
\n o9 z5 wy vi ya ea ee fo gf va ov ww rr wr lb ro qq vr gj nw ru ym iv s4 hu tm wo wp zs br fs wg ej du y1 yt yu e7 eb em dd pq v7 cr um ae oz 0z kc tq rw zl rt wb y9 xv tm tq di eo te gc
\n tt un qs qn a7 qh je qj k0 o1 wr q6 wy ab q9 qm wr ea er eh pi hi sc hs m6 w1 bv lo zr tn yk ep op es ve xx sb ux hg sa gq qp wd n2 zh wf xf wj y3 wl e7 os u4 on ip kn ko qp s7 ly zn ba wu u4 kh f4 zo y9 q6 oh iw tq
\n qa a4 gu a7 cp z1 he ma q7 lu dp w7 ea rc ee d8 y4 tw ez im ae bv ii qe vb zt lc lv wm ro lk qr hp re tw yv es fp as zu oe qu qi bp wg cp p7 v4 ek rd wc ar rj tj e8 od e0 pm h2 h4 in qf wu wi 19 bj rl rc ee yj et tw ep
\n gv qd kj cd t3 c3 ih ws rg mc rx lh fd g8 gh cc vw b7 qe at j7 qo ws wg oy t6 t9 go eb e8 us u5 rq oe zj jy oz cj wb be ei pm og se w4 yu xw su yx if
\n o9 ub rd hw gs z3 ql nq ru wg jc 1t kv mr zm ah dd jk w8 ej aq ig y8 pp fj li wq jj cc qr no wy wu en bx yr qy oo es fy pd tk ix ph yr sf vx pn p2 jq fs ed oy yk os ie s9 u5 ak ud gd uf kb xc u1 xm eu xw 19 wn vh w1 to ee er aa rb rn ru an r1 ei
\n se kl 7h b6 xs ym tp an ta qb gn uo pt xi cl qp qy op vr ym ri ti tl i5 e1 e4 i9 ff i5 qp jx ht ql uo en pe ku h7 iw wn w4 ey ia si
\n ql xt wi k6 ew sf eg up eh oy sq ja g9 i3 qe cv l1 qq bv w2 la eu wg ec ef oh fs tb pc xd qs nl qu fn dy oi iu yf re fc hj hk xv zn zz w1 ew
\n po al hm qk jt cd ju nm li rs w9 ev ut ea 2f r4 d6 ey im pa nu wr m4 is bc xz w3 eu tb ha ft p4 ti to hr dy af i6 iz r4 jb x7 wj xg na rf gi at pn gd re wq qz ze bo wc vz sm zo my ye u7 oh dk w5 is yx tw fe dp
\n jl za gk cm wu vq jc zc iu mb oe fo fp ic sc 2l hy qr eb p5 pf dq pa fy lc td sz oo aw u1 rj fl tz nx aq xx oz xb 55 y0
\n uq wr lh jv ri i7 ss qo gy bt s3 u1 dy ox hg it
\n ps hr lf jx bn qq up eh ab yl pn jg ng bz gd qr yw i9 j8 zi 3v oz at hd cx oj u9 rt uz ro ov
\n sq ga ny se cj id rg r3 pk kv ee sh ek dk sz pp q0 mn az kp ei qi ry em ph p9 gw hc m0 cp ea mn yf t1 5y wx ol e6 ec u2 e7 uh uj uk av ql lw qx zr qv mw qg cq ww wb pw tu w2 mf ut gk af yo ie ob
\n hn um a6 q7 af du r4 up tp ej sk lo le m8 rp eu ei qi ky op of tp ur oj hu tb dy qu gt tf oz wc s7 e7 ua pw ax nb wx wy fj wn 18 wv es yq ok w4 uz yx yc
\n pa qg qh q4 fv qz kx q6 cp gb c6 pr eh id in qw we bk wn qq b6 qy qu es ic s1 og gn wp op qf ic ro os yp rj fj ag oc ay da fv wl qp f1 yx n7 ea w2 ly yj iq iw rm o5
\n o9 ps d3 lp wr qc md e5 rk w0 pm gx lf ku qt qp to tc pk fb tb qi lh nt yd vt ot ra tg gd zx wx vj rq cr hm ma jp vg u8 rt ei it
\n dx dv h9 rf qf uw a8 qh uv k3 ri is yr r3 eq uu tz yn y6 qc ps jf wq xe wx lc qr j4 ku xx nb 4z sr tr uq p6 uz of i6 s1 fs pj tc hu qu hz f1 hp lj s4 qx tg yp gs ob tz ds sw pm ug hm ip ql le vl wq tb xv eq w2 yg w4 st o6
\n qd q4 pa z6 qz ia 70 r3 mb iu es r5 gh t9 cj vz qw mb ko vt qr qt gh qo ty eb kq n1 xb ef rp ek gu rg s7 rj sn ai hg o1 uj pr jt fg v0 tq tx ww bj bm ct w1 zi rn ox iw ri
\n al rd w8 vp yd yk r0 pi po se sr qa l0 qk ir e9 hm kc rz aa w6
\n un pq qd a8 z2 qk z5 ws bi xy qx wg wp t4 mj gv qm rg c6 w7 w9 es y1 g2 ej yz gg qc qn wq qw m9 wx qe kr 27 fp fq m7 xp 3p qr rr tr ij il eh au s1 uc fx ut qu sj j8 j9 ya nr rz wg wh eg x8 sl t7 yu vf ay ds ap re dh qg qh qj hz qk zz qx k3 cy iq ox qv eu nx n6 6r lq n0 y0 uq tb sy iw fm an
\n yv dc qs gm q2 cv ok wt b2 cj wu mr zj kn e5 iu pz r8 pe fp ot tq a9 y5 sz ez cl wq qq wv a7 ln ky jd qe qr yx rm qi ea ln te y9 ev en eh iv tx e3 as tn j8 wf xh co fl nc wk xz es rx ee wh ub aq u1 ar e7 up it iu o2 wl ko jo cu pc wo al hm uq rn ul yz ro
\n pw na wu jd yf oe qr xr sk wa hw ql wg x6 s9 u7 am
\n uv tr ub k7 qg he u6 jt gs z3 by tn bi av z7 jc ck q7 2n ny cx km mk rf pj xi lh sf up yj to ia ab tq fq pm fd qc qv ps su qw fu xu cm zb bc qr qt tn ei rw gl p1 xi qo tt ed ef ri iz yw oh tc uy tv as qu l4 qr t4 wx e5 ae op oa em tz gd dq rw ug dr ux qj be ko cg nl je aj xw q1 vv ax rl w2 yt aa u0 eu ah
\n dc ph sq jt ql un q5 cg lk w9 ur uy pz uo sx qv qq cc ln fu ym ho su pn qa bq pd wj wj yk ou wl rk o2 pt uc km ja wm ry rm ob
\n gb pw qf we q3 ls q4 sy bl lg q8 t3 wl rg ed io ef if oi hp lo kw wy qw ei yz rt es p6 fp hi qo bn qw wg cy np uv yy oa uo ir of em ug x9 qh nj n8 ea u8 er w6
\n ij dg cd lw gk wu zl dd eb eq sg ia am in wq xt nk wr xj qq p5 pd pk as sd fn lj jw fk l9 nt wl oo fj sb u4 gs fx hg o1 dr fb hj h8 xc yq ch er e2 aa af ah ob
\n a2 o0 hn pd iz hw jg q1 jl qz ip le me wi bb r3 z7 g1 eh td sw g9 qq c9 vy ud qo es ec tj uw dq ur hj dy oe zp lk l5 fl wj ys t2 ej t4 ek rs sl yu oa u3 gd pm rw h1 pr h2 py wl 2p s7 wq 6r mi 10 ox o6
\n i3 qw ee ur cy nx r2 wj t2 ub ir aj cl qm u0 oz
\n qd qn un qz xy nq an kg hc c6 w8 93 eq ts g9 wy mg w3 rb 3f wf rw kt op es ef at em s6 pc wg bw x1 xl wg hl yk yo eb ud hm hl py wb u4 zp bj bm se sr sy ox am
\n rc ix qs ls qy at ut pk yo ys ec hs lq xv ks
\n yb al zf ws cn ac ih th ww vb kt b3 xo qe qi te ea p8 tn qd ci ix xk pk bg rc tl f4 wb rb ru
\n iy qd a5 jq jw qh sw fv oz cj hc qq ya ee yn pr av or us iv fa qb q9 bh ns d0 qe i1 b0 fh qy qu qi ry os ul hq ri ix e1 ao p0 qt sf qi uh ll ko lx nz sg jz hq sh p8 x3 wg rd sx yo yp u3 pv rq ds tc rr wx lr xb wn ep hh bk yw q6 og yr yg si tq do if
\n hv qa qf jg he q1 kj qz bh lr kn rj th kz ef eh av pp i1 ar gl ur lr bz xp yr ze qt tn es fl hw s5 qa ed t4 wz sx rg sv e9 fz hf al h1 av bg ym ee yg
\n k8 nn jy q4 wd lf xu q9 a1 4v yd mb r6 yh pb ta g6 dn d3 pl j1 jk wc cn wy 26 rr te ti fa e4 uy fb gr hb kd lc qf p5 wh au fa iv xo hf ot eg ra wv tp ec yo ah iu pw hj ac h3 py k2 u1 wb rl rz yt er w6 ru af yo ep
\n qd uq qh qm q3 vg qc c5 rd vp ut eq on yn ii xp up r8 d0 sz qx ue pl lx qe wr qr lm nh qt ha qo ki ri e2 tx iv ao s3 ow kp xf rh ya r2 rk cw nt by wd j8 t1 hk y1 ns t6 wc ev sq rq yf ux aw ch qs u2 zn sm rt wb bk yq dh 8w w3 rc yg o3 yi ox ov ir
\n u0 q7 qb ml or nu b5 1l xb tr tp in qt hz so v6 dq o2 qh wl nb rv fw
\n ss jr zf zh xt oy hy aw y8 js ob wq ny or vy fi en tb qi j9 gt ib ot oy rd e5 y6 tg th pt gq wz rt rl ew fm ie ri ir ro ah
\n o0 qj h9 wy ee g9 gk jd fg qt 3d fu ru iz tl fd tv ad hl wp oo wf nb ez sv tl f4 dr oy rp
\n ak il k6 qh q2 vd k3 zd bo lj k7 km 5c ut rz yd up ua is r0 qn zq wq j1 qe cv pw fu md bw yw qq ra rw qu ex ik at y0 ru ti yw fz ic ao ow gm jc i7 nf p4 fj xg kr br xk bs mb pk hl wl ta ez sv e9 us om rw ap gq wl k2 qz h8 gu kf et ru tq ag uz rp
\n yb az dd fu rf hw qg we u9 o3 q5 q6 ag c2 o7 wa kh w8 vo mc yg tu ua uh ta tw ih hu fj su bg ww bh kw ry ru wy ky wu wi fw 20 b9 qo ik oa ev hw s1 e1 e3 fc uu s5 tn qy hz jc do ou jq gb kf pf xl x3 yv lz iq eb e8 os sn fx dw qg ql wc ka n8 gf ly se tv yk di si o7 r2 rp
\n il mj vi sd ia y6 wq rm p5 ux ho nr ef ej wq iq fn
\n ft cs uo io er ic tw ig mm c9 xk ab ze uw i5 s1 e4 pl ui f2 lj p4 sf x4 kz ej ez eb ov of rw dy av qh f0 h5 ki qx cx eb og gk oz uc
\n ul io zd kn w9 y3 wt qq wp jl i9 jk ca h5 wx wb tm do
\n iy hv cs a2 ee yz y6 gk kq em qy uq ts w0 rq rr vt pb nc q5
\n qn q3 vt vu yk ej fp tw zm qq qy y9 hh wo wg rh ep x5 wk mr el l9 av hz w5
\n hq qz wy cx rh ur w9 e8 r4 fq im fj gj dm qn gl jn iz l1 yh mz rw e2 qo wh nt wk zw t7 e5 iq fh eb sn ud az uv fh sv dq q1 ku zs eb ue xq rn o6 do
\n ub lo sq wr d1 mt o7 ts t5 rd xe iu yg ot gg se pp qc js lu xt j3 j4 wt pc vz 5o yr qw zw qr eu db sy eb em fo i0 ad gw m9 ig ih lc od n4 pg rx bi ni kq wl aw e7 az jo mk bo wb ei mi ep wb eq di do
\n q1 ub xt db wt ws ik pl ee or to ej ic is fr jk ls c9 qq yg qt eo rw tp p8 dy pz gm hz or xs bt x8 t4 t8 s7 oj lt wv vx u7 w4 et ox yo
\n po o9 ih dx qa rf qf pd d2 kl ad lh kb bd qm bb b1 z8 ew d6 yg d7 ym ti eh ic iv oi y6 sz dx qn ut qm gz pj zw jj 4d bk wb lm xb ke yx oo qp yb yn en fo yw fp e4 aa fd jz qu gw qa zs nl v9 wf qt qi vg ni wx hk 9f sz tg t0 ga de re io av h2 jt x0 h4 wx wc fg rb rn nc yz iy zp ds ep zw pr xv rz yh yk zp do hc ep
\n hb ty z2 qz qz zh gw mg kb ve zz ti tp py el jp tg qc ar qv gx la qr cn lr nd ng ve qt 6g ml op pd uq uw eh i8 uy dt ho j8 wp wd qe xm w0 x4 qk el e9 pb sm pn tc gt ce oj jr mi ds wb ym ew u8
\n ij yb hn u7 cd gj co dp lp b2 r5 ed ti pn qx g0 jb jn jj we bl ri ot pi rb yc sv ty oh ph hh e4 hy sd wp ll ft l7 wh ca ys wf wb t7 sv uo sb sn ha pb sw de un qc bz wo en as tb eu af eo
\n d2 k0 wr q4 q5 c2 sj iv pm g8 m1 l1 5s ij aa lb xm vf ej ta ar th od sm cw gy bu qd q1 u8 ry rn
\n qa ux q3 mj ex yu zx rk gi rl ya is py am tw ja js db ps dn qb qn gn lc pe qq vr qr eo qi ec oa ev uz yq of in ho qo jj jk wk wd zp wf lz t8 tk ha pv fz pn ug o2 pe uk kv gq v7 oi qv wv dj tv fn fw
\n dx a3 k5 um uq jd og nn q5 qx cu wp rd ws d6 px ac oe rb up tp ej ek ih ff qc gj qm xk b4 dz jg sq jh eu yx eo re es ul yw tp i6 pj ho qi qf sn og xo yv pk wj wb go ar uo eb ir iy pq uh qg h6 vt wv sn n0 rx af uz hx eo
\n yv ub ty gn gu fu dm ca q2 d4 cn ad iw k6 bf zl zz 2o w7 uo ee yk ix g3 am fw oi jo se ha vs qn iy qq 24 bl j6 g4 cw jv 1l ei qy ke j4 qi ep of ao hh tb gm sh lh vc uf vu wd p6 xm qt kh rk l9 s4 wh mr t4 oi rf iq op ox u4 e9 fk u5 it re uk f0 kb nd qk ce jp lr cy js qd qb sb tq n7 n8 ed ue tn ox o6 id r2 it
\n qa pa jd qn qg jt gh q5 lg ag qv ah qn vr da rh w7 b2 rz rx d6 d7 eg eh yl a9 ek dl tw sc hp ha su gz lo qe le ns kt qy qi 1h kp mz qu es yb yn p6 eh fs ok as im dy px gq qp qs l6 iv rl zw dr 4r hi wj rp t6 go s8 e8 at e9 f3 ak dg f9 qh pt dz ww rv wb oc pv be wq cs q1 xr xx eq yr u9 sr tb yl tq if hc ig
\n a5 co dh bt lw ck lh w7 3e mp r3 rz yf yh uh eh td y8 fg pa ar va dm su q9 d5 qw re vh he jc 1g ib xz qq qw yg vt rn rb cb ry ym em i7 hr ff f2 qp rd lx wg lb kh va jv qi xd wh wc el un sz tf gu oz ae e9 e0 iu dr io dt fb dh jo um wx s5 oa kx ly rn oc zy f3 hb tt wb u9 oz hx if ig
\n ak o0 qd q7 eq g1 y2 pt dk g8 qb vs qe dh 5i pt yh qo ul tp oj sp oq di uh zg xn rx tp tf ie f6 cg rv zm xw zq 5f md sr yk ru ro
\n a2 tt ub rs ij ml ow pe el gd va ue zm sa pq lc yw qi qw lv ep qo uj ym tl ye hj s6 uf qp 82 fk y1 wl oi t8 fk pb tx o1 sk lm oo xv n2 ad fk n6 dp on q6 rv
\n qf jf kk nm oz q7 b2 xo fw kj rh ua oe yl gh vd qe gn wb pt wi z0 se gj 48 of i5 oh so hz wp ae wg nc kg xf ev pv ov au iy az f7 qb q5 eq yr tv yy ol ry o4 oc di ep
\n po o9 dc a5 jd z1 sq ws b7 ti r9 sl ez aw tg zm si ng qe ky b5 pp eb od jl ff oe ce qp gy yv qk r4 xf kw iw sn tx gg uh cq ql qa 2s mt eq rb dp
\n qs qz cd dl se q0 lv eu yi rw qo uh uj ul en tx wo qd e6 pv gg je zx kp qc q3 ye en
\n un qs qh se ws lf so eq yf ef y3 g4 zb hs q0 no qw j2 y0 uu fb di f1 kq oa ul t3 ot fh ak yf fv dt f8 jo sx wx at wn cs lq zc
\n ub qa qs ik pw uq a6 pd dm d1 qm d2 qk cv zd bi wd ne ah qb kg kh ij 1p rk w9 wt r5 d5 px uf eh yk oy pm i2 hp st qn si qm zw we ls px lr ri qr sr db hp qu xk fy os eg en uc ur i7 sa hp vn qs kw dn od rh xj w9 wk ph ap yh el oi oo e7 gp ay s0 f4 gf az jy qk ql qx 1k v9 qc jq zy n5 kg hd ww wv bj hj ur er rn ry eo o7
\n df a6 dn je ql no q6 ox wo zl bn rh ya mv e0 yn pr gd pi y8 i4 c7 g1 j6 wo rv eu xg eo c0 yx ea sv os wp qw wl ou un t6 u2 os of f6 dt f0 jt wc ja ae qv rm ds pq y7 qk ck aa ux
\n db iz jk zd wy wh c3 zk 2o rj hw vp on ed ac to g2 r0 id ta th qb dm pj m8 np oe pu bb tc gh ml rq uf tu eh ye tx gv pk jv j8 lk xs kd fi mx be wd t3 mr wk wl td eb ie tz dw rw pm re fb dj h6 ql wz wx qx qv u3 vz xe ex 2w ty ew xm oz an
\n ty a6 we wi ro lj bn rh r5 g4 aw jd q0 gz xy m6 wu qq et oo ex qp tr y0 fi au e3 oj gm px lg wk tu ek tg u2 ov em dg uk nd qj cy hp wq mi bj q4 ia fm r1 ei ie ux
\n tr qg h7 qk jl kc jr am mo w8 e8 td gl kw sd jo qr vl gs qe b9 mm fh eo uh ft ik e2 i0 uu ff qu f2 jq v2 wg kg ek aq wm yi yo s8 e8 sq ab cw wt ck pb pn xl bj yq wm ew xq su r2
\n qk wa q6 jj ws ut gd gf ly ec pj sa pd wl e5 wc da kx zk zz zb wv rm te
\n jq uz nv ql as jx z8 q7 o7 yt rl ea e0 ym y2 pr ia sz sq sr qq qr vk oe pe lr bl ll rm yx y0 eg ti e1 ue uu ui jx zd oq kd rg lv lb r1 fx ro me ts ay f6 fc io qg py qj qk qs ky qh y9 ok o4 am
\n qh dl jt wy a2 yk y2 i4 zq kq we bb dg m6 qq zw rt ta tc ff xs xd qf g0 1d yg du wz iy sw tc dd hj hk mh ov zi wn hk ee yj af
\n ra ak uw q3 cb ji wy fw gw t4 mu ts qw ww rj vo rl yd ug d9 gj i3 zw qw wy md qq bv rn qy pd tl ic p9 hr dr hh ui sf f1 i6 ws cy es ef t5 kr ek oo t7 ec e8 u4 od dq ji ch jr zm jy q1 zp yq 2e og yo tm
\n tr tt qa qd jf pg qh jr sw ao q3 qz za wt js bl vw q9 ws uu w0 ya pl yf rx ee tu r7 gg dv it lo ww up js qq qe lz eu qy rr yb ri ay ye ta tv im sh ss uk qd qf bw ro sl wl t5 e5 um th ha fx re ii fv je hk ot cq km h8 ks bk vl qn xe te tt rl u7 iq ry ag dp o8
\n sa z1 qj q2 nn wr z5 mq xu q7 gv t6 w7 r4 c1 mb sd ed ym ot ta ht ts tw gd tf g8 se ar gh fh qv qn zm hs qw qe oq wc xj vz xl wo rn i2 sr rq at yq uw s1 tx hy fm pc wo hv gy vu wd lc ul p8 wk wk el oz oa rh gp pn gf fx fc f7 rr dy x9 uk f0 py wl v7 cr ch qs wv nz lv lu 5o xe ym ly er yi ia gl ox r1 dp
\n uv qd hm qf gp k9 kj we lf bs ej 2i el wl t7 rj w0 rz yf ys r8 tp py tq tw dl im qc db qq sd ry c9 oe if qw aw qu uh tt p5 p7 p8 oj zi oe qu qi lk j9 sk zs ka lc wh wk zq mq vh t2 ej r9 mr ez t6 e5 op rk ga pb dq ap f9 py qk qz wc kd pv bd sm dr u7 mf o3 yk di r2
\n po a3 uw q5 q7 ck kb zj td zz yf jd wq xh ld qr w4 p1 ij fu tp qq sv y2 yt t6 e5 op dw iu pw jp ka qv 4u qf rm vb w6
\n fy a6 qg cs z3 ql dc jz wy me cj o6 ba kv wp w8 ea r5 uo fw ib ig g7 gg sy bg qr cb cq ro xl xv ex tt ru pd hg im oq gq ao rl pl aq sz t7 e6 os uf ug gg pr ql qz vt mj px wb ci qf ov be bg ww mp mi rz u7 w3 ei yc
\n gh cm ca rg uy pm y7 g8 lx yc qi re uh yn uq eh tz ph wo cr sv fp kh sl oi sx ov ga iu h1 je fd rv qv wi jy yy ry o4 tq si
\n qd iz qh q3 cg lf wy xa ez eq om ug eg yj fo fp yz qx qe wb or jg xb c9 p4 tj y0 iz tc oj tb i6 p1 ka zf qe yp wj mv ra ez rd uh pt zl lm sz wc lr bq oc zq sr af gl ei ux it
\n a2 qa h9 qh q3 fn kx ve wz us sj yz fd g7 vh c9 xq xj ln tz wp wf kg kk by vf j9 5y un yi e6 rh sn tx hj kn rb
\n un qs fi qm jk js bd o8 bx vt eq ya xp yg pz ym dj fp tp ta oy dl qc cx qq m1 rt wn d0 wm yr qw aq qt tb ha p1 uk yn ef tj gv im sd hk pz jx zi wa wf ba l0 wd mq ej wv t5 ek iq pv ov f3 em ak rq hn hh f7 uk qh ot ju ng ji h7 wz cr v9 bj bk rc er ia is iw ei id ov pu hc aj
\n pw gm qd jf u6 z3 jl q4 wt bi lr id wa wz w8 ya ev ew r6 yn ee io r8 ip ej td im si j2 jo d7 m4 pv iv yg qi il ti i6 ta ib ap fb hz wd vu wf wh kt kh og kk nm rp ti ek ns t7 y5 wc ae ir pv hf ub wc ho wb wn mi rn w6 yk tm te rp
\n o9 un h0 a6 pf iq xi tg w8 z7 r5 om oq eg or y4 sr fh zv vw zq tc ws rq db rw eo ym tl fv i9 pz jx j7 hx oi qf x2 l9 x3 qj by to el sx ys yd ao az uj hl dl tj nz wn kg kh on wv w1 w5 gl ei
\n gv az ql nm rc r7 yl ja zn q9 xw no iz qt pk x4 l4 tg u3 of zk wc go qb mo eq u7 tv rm ig fe
\n o0 ik qd um qg k9 mb bu wy bx ny ws hm ea mb iu pe eg ey sh uh g2 ic iv aq td qx ja qb ha 2j lp xr wc vl wn wi sl tx qt rq ec vw of uw sm ic qy j7 ns hb 2q kw wf vp 1f x1 5c zs y1 rg tg oz e7 fh eb ie up e0 ap ve wq zz cr wz h8 wv go ly fk az pr rz h4 ew w2 ok w5 ia si ro am
\n dv d2 cd qc zt 25 xp wd te es sh eo wn f4 wo tv oc
\n uv qd qn hr bj b1 mw lg io sh lo qq xh m6 28 rn xx p7 im qt jn jw bm qf r1 mn ny ed em ii dd wn cz ds vc wb hh q4 yw ur rt ie o6 ux r2
\n rs dh z1 jr cb vq r3 eq om y1 sg r9 if tw qb qn m2 vy dc b0 ik fa ib aa jz qu sg qs 1s be of w9 oz sv t9 oc pv rw dd o2 dh lq ka lu 1m qk q4 y0 ye yq w2 w6 si ob it
\n o9 db iz fi qn qj mw 3v wp li e3 km zz yy mo ya rz mv yg r7 yh pc or r9 pm a0 td ih db lt pg jf gl re ww qw m4 j5 xi wi yd nh yg qr rm et ey ug re rr y9 y0 sb tu od ay of iv oh i8 ok uu sf gq lg wa nn uj qs cu kd xb wg cs 3k yv hk pn ii hh cw km wz wx wc n3 jr wm qn bd zo mo jo wm q6 w3 rt an hx ah am uc dp
\n ps pt vq kc bs vu vo xu ee ib hv wj x3 nu ud yf qh wb lb gz
\n ra o9 qs ty rd ps db pu qj u6 k9 nb qk oj ql wt jx bo ri xo o0 mk rh bm mj ut mb rc yn et yl pm ih i1 g9 qm xq oq bj wt jp xu pt bc eo ep qi ky sv uk fy ri i5 im dy hk ui tm f2 uf ug qd kf nc s3 fs 12 t2 ro du wk ek yt ej t7 s7 oc ay s9 pv fl s0 gf tx fc ac py v5 qk ce qz cr jp ck hp u3 zm xr ii yv ea cf hj ye w3 tp do tw ux
\n o9 qa ik wt kc q8 wk sp yy w8 w0 ys ea om tu yz pn fe ae g9 ps g0 i4 qb fk qn qm ut j1 d6 4d cb vj vk xy j5 be wi ve qq gf qr j3 ug qo p4 sm s2 ut fd pl qt jz ui qy qu qa nk fj iz xh wk iv qz fb ro x6 ti sl rs hc oi wm us ai dw o1 hh ab qh qj ju ng wl zr vk tw 5a vc hk md yr u7 w2 yt tn eu ul ah
\n uv ra qs ty jh q4 o4 bc vr o9 rg jz mc r4 ui g1 ey g3 sj am sq fj qn it xt ln dl jh b9 g8 dv qt yz ea ue ss w9 wj kk bi ym tg t0 ob ys iu uj qk nf v6 nj ox qb wy mw 1n pq eq w1 rx rp ge
\n a2 pa e4 xy yd sj vs jj xj lm qy qp ri ux p8 pj tv xs wd wf oz gd sw rw uj uk qj k1 xx um eu bx my em ey
\n az h0 qz iq bl kb xp yf y8 qn rv hx oc re gt k2 bo qg cf rl
\n yv uj d2 mq hx ws w7 mv yn r8 ab an ae jn xw al up be qr zr ep re qo ec ur ap hp pn wp i9 rf wf vo qk t8 eb yd uk kv ww wx wt ox kh mi eq yj oz fn ie am rp
\n ik df qg jg k9 wy kc ro wi ve bb rl ew io or eh sq oi qc qe d7 m4 pu gd db oo yv yq ix eh fl pg ib hu pl cr fr xd cy ke mx yh wk ag hf hk qg sj we mz gp u4 ak ma rz rv af ox di yx ob
\n hn pa pw qd il qh q1 z3 wr t3 wo ws vu uu ld pe fo dj ot dx pp vl qq rr ls j4 fs dl ve c6 rq ln xk ec rt ty ik y0 tu yq fz s5 sd sh jn wa uj ws lx qr ca rz wd nu ek yy yi y6 uo os up f4 fz qk h8 qc wr at 18 ca ww rv sy ox o6
\n dv wu wo uo m2 we rp b6 qe ik e1 bq w8 x5 ez fh u4 iy jy wu li f5 u8 w3 yl te
\n sa ds qd no q5 ra jd qo ru r7 uo ar ud on ak fv dg wl qx qv ye yl ep ge
\n ss rf qn bu gj rj uo yz tf m1 kw zr oo y0 pf tc dy qu v6 xh t4 oo um df je qh dz v8 ho wn wo 0w dj rv o3 du ro
\n ij uj k7 me lg ih hv ws rj pl sd uo y1 yk d0 pt y4 g4 ou tw sq td fj ha qm qq 4a kw d7 xy m5 bx c9 yx nw tr qo uj fu en p6 s1 ht tb qo zp kq x2 wk wj wk yt wz sz ae iw ay fk ao ug pq qg k1 ql xx qc cl qk 56 bn oj yt et ut uy tw ir yc
\n k5 pg cp z5 wr no zd tk ej an qx gj i3 su we up 3q yq fx ib tv qp ik wj yf u1 os rk jt qo qx n9 w1 rb
\n k9 uv gs wr 3b mh km bm we w9 es or yk r0 g5 aq gf nq qv ll m5 yd zq qt qp sv ed p5 of eh i7 pz hl sg jn wa m0 nm kf w8 wj de e7 ar iy pn ly wn fx w3 rb ey am
\n o9 d2 vg gk ex rf rc hy qm j4 ga qw rm ls yl cm en tl tp fp tb i3 qy qo j9 vn zf wf qg mb kj qi jb mq wl rj s8 lw um zt wb f4 xw f9
\n ra go ls qx wi c6 b0 rw g1 yz fe g8 ow qq ra mz ex oa fu iz tl uc e1 p6 x1 tf rh tk fz ap hl qh k3 xb mw zm yb yw q5 aa rp
\n yu qx sc xe j2 oq gs i6 i9 l0
\n yv ss tt gu fi qj bt ql ls io nw gk hl up zv gl ni xt wy dz qe ud nw rw qu uw e4 qy px qf zw za ty ek t7 pv dg ho wn uq rx yx ep
\n ga 1w ld wy o7 xr pk r9 g6 hu jg lx sd no xt wr zy ku l2 nw 9r rt i5 to tp tc s6 f1 ud ko xb rj qy es t0 f4 fx ii rr hm hj fb ji oi n1 vk ci 9e mt yc 2r tv gk yp ux
\n hb hn k0 wy m4 w7 rc ts y6 j3 qe ve qy rt so di qo dp lk xf mq wl em f5 pr wl wn 3k ew yt w4 ri
\n qa ss lq wr bx t3 r5 ed eg sx dn we 7n ra qe b9 rm wd rw eo oa ri e1 e2 ut ap hu qo ws uz ai tz nl cu wq ln wn ie aj
\n yb rs un hm dg qm qk ao mw fn kv ur uo pj e9 sf ia tp tw a0 td sx fg su xq m1 om na vk wy xk em l1 z0 nh b0 mz qy p2 ru au iv p9 pz ug lz xn wf xg fk zu wj wd u1 e9 tl ak hf sw o1 pw dt gq v5 lm h7 nn nl wq uq zt o3 ad ry id ig
\n a3 pp dv qs gn u6 jy kk io dt wt ck rg ua yd ya yh ax ac y1 pe pc pv fp fw dc qv zb dn q0 ju jj m1 ui lx qe qr t9 ja he wi vw m7 l1 jn qe wa qt xg rq qy yi qu p3 yb ed en tz so s3 tb ho fm px gw zo lx wf sm mc dq wj yg l4 uv yk tp t5 wz ol fk f4 pe kc dj wz qb zm wi br zk ww ty 6i vm eb lt eq w2 ey yp yz o6 ei
\n fu ga io nt wp jz yf rv oe eh pt dz ih sx qx g0 qm hf xe lz gc d5 bh 2z cn d9 1r sz li bv qe 6d bb er xv yx p2 ea tj p5 ay uq dq pg oh qt s6 px sh ko qa nn oa bq cs kk hh cr wg tu y4 t6 e5 oz th sn ov u5 dw qg dh uk n1 zr qv 3d n4 yx xe wv eb h4 yo ro hx o8 rp
\n ra gu hm a7 jw qm qh jr gs k0 ql xt q5 dt ru wy k5 wh fw lu kb am m6 bx vy qq ev rk 2s mc yd es io pc pv g2 ek tq tw in ih ae qc d5 ui qe wt qq m8 vr nb ee hu rw rr tt ed fi em e1 e3 hh hi sh zp qo wp l4 ws qf qf pg eg to un gu u1 t9 ox e7 u4 od ds de hh py ql h7 gy js vz gf y8 uq se do ro rp
\n qs qg gk ta bf r3 hw r7 r9 sh ua g3 sq td g7 ha lu qw xr wy wu rz ko bb i6 uy as di qi za hv jw rf 1f 2u va ap qi rc du wk yt t7 u2 ob re ax v8 cg qb wy wn kg pn yi rn ru
\n gr ra jq qf go ga jh gs q3 tm q8 k7 o8 mj ym er ip ua ej hy i1 dv qb vg cv m5 wy xk g5 wi ng w3 3w ud rw ug ep hq ta fc fd aa i5 hx qp wp v7 qs l6 l9 l0 jn ty t6 ie rj tk od ys on pq tc zl nh qc xv wc cu ks ei lm vm cj yi ad r1 si sp
\n qa hm a3 ac q9 na rj if qw rr vw tj ib su qu wo dp j0 wf pf 2y wk ym ra wb ae ga gs f8 gq im ar pb ec f9 yu rm
\n t8 ej an y4 td ez ln z9 lj qy sm uw dq us cp nu tg vn
\n qa ds k5 hw k8 k0 ql hl 1r wi fw c6 w7 mz rj xy r4 e0 ym yh eg r0 us fs ib oi qv q0 ww lp gm ln bx nc qi l1 qe wg ea qo eb tk eg to ur jc oe dp hv wa 2q nk at rg wf wg ca xk jn pk yg er ot uv wb aq ol wx e6 ev sv uo vf eb ah ud da pe qg jr kn ju ng ae wv n3 iw ly kf cl pb wv tt vn eb vm u7 ew aa w6 rm gx r2 o8
\n q4 q6 vk d6 eg pc pv r0 tw i3 q0 we tw sm e4 ow sn kg up hm qx zv nz wm u9 ul ri do
\n po uv dc qs qd hm qg q2 jj sw kl me q8 xa wa xf z5 yd r4 rq sf px ti ia r9 yl dj dk ek qx i2 sr qv qb lb wi nf wu qe tv fh qo rr yb fy eb ri ai ok qt pc ud qi qa ws qs lc zh nc x2 cs t2 di ke wk sz oc yp s9 ys ai ln wz cg wc wv os qb f2 ec y8 dg wm rz yr ee rn sy du su eu fm ei o6 dp hc
\n ft qj q6 wq up ut lg er uw db ll ws of og e4 1i r0 wx fh th vf re hm zi
\n a3 ty pw ph cg uo r7 oi q0 lb c0 vl xx mh hu b9 qy tt sv p5 eh to hh ow tm oe si sk oi gt kq cu vi j5 wf el tf yu u3 ya uj dy qh ql ct wc el y0 o3 o6 if ge
\n a3 dd by wt lf 2v bl 7c bn cf yo go yf ii et ey yl aq aw g8 ho i3 qb dn qn lu vf vg 2k le ml wy t0 4h xk qw b7 bb eu xr qu tt y0 os sn tl pf og tz tx pl ss us xd cu oa qd xn ke qf vp kh ny wk r8 ej rd t7 sc e6 rh ud tx al gg re hj ux qj gw xx zv xm iy ca vb yw en oh u9 aa w5 w6 ul oc an uc
\n qa un dn hq hw d1 jr jy kk kl wt kz zf z7 cm q7 me xp wp mj rh ue e7 ys rz eq ew ed xp ee yj y1 to fw aq po i2 jn li on m2 na vq wu ck er yu db yi gl ty eh uw fp tx e2 fs uu sf jx oe jb qp cy bn qd wg 3z nu j9 mr t6 gp pv ha tl ai fx uf fc kx qh gw xb zt qv qb ir cq vb y9 ct ol fn ah hx sp
\n db wt cm ch jc wi dd ys on td po y8 q0 wq kt eu tc tv or fr s3 na e7 uf gg re f5 tt aa tb ie
\n a5 jw qh q1 qj oj xy my b6 es yg yl y5 zm pv qw qt qo ea ri ao in s4 gv i0 ad lh wa qf gm rk vs oy r0 ez ab lm qs qh ry ox
\n ga ca z6 nr wo rg bm vu uu rj e0 ui io pe eh d9 ab tw fe tf fk wy ln md rk sk qq qw hy kp dc qy y0 p5 p6 p7 ic pg e4 jb ge wp qa bn xf ks zi oy e5 um wx ie yp fv je ng oj ja v9 bp qv er an pu
\n a4 jw a8 o1 q3 un gh le nq q8 ig rg rl ea io er tu fp dk hy sq ae lo qq wt wy 5i xj cw dz oo qo yn ty y9 y0 sb ef tj uw ta ur i8 tb oq af hz qi wo sk zs qs vi wf kt nb y1 oy wk r0 ol ex ec tg t9 eb ap fv qk ji cr s6 et xq bf ep mi ax rt yu iq af am yc
\n gr uv hv tr a3 qs lo u7 jy qz kl z6 gk gz ag kn rg k0 w7 wr rl pj ii yh up ac ot d0 g6 td y6 fr se pp dc g9 cl gx qw pl m1 ii qr vz oy nf eu eo p1 os y0 ri ix au uc ai fx p9 nm jj lz pa kw ul rg gw qh 4m eo qc l5 rp oy ej un yy yu t8 sv ud fx ac dy av gq qj ve sl bu th u3 rn nz n5 zm yz bd tx el ex n9 es rt rz rx ol sy rn yo
\n ph cb jx wu ib vb ih ty oy tl vu
\n df qd k7 z2 q2 ju jz zf cm mw yr gu rx yh ym ef pc qx jd q0 ow pw wt rj xo mf xl qq qw ud tw ku ik oa od ti hk f1 xs qd wf dm s2 ph xo ou sx ae iw t9 eb u3 rk ak hf dw ax oe zl zz wm sm el cx cw lq za tu yw rx yu rn fn yi ei
\n a6 ql wr jx z7 wu xi ym fo if a0 dv ww lx zv dk tu sn hh ff hu zo ws rf wf aa ni kq uv t7 um go e8 ob sm tz hl uc zz ol lr kc n6 bk ry if
\n qs gm tn rp iu pi qe ec to l1 wh ra wl it kx fd vx q1 ri
\n gu pw qg we d4 ws q4 cn q5 me qv zj zl ex wr xo yg r7 eg et ey us iv po aw se cx az lb nz nc qq ew rs rq yx ep tj uq eh fz hg gv jc di wp sa nc ya cs fv qz ti wn aw e7 ox u6 pn re o2 hm fv qg hl dl v9 qv tz 6y rl ye rx ur tn eo
\n gr qj z6 ld tm jw hc ed y5 se ke ht tn jb 12 yt ek ao io wv ew ey fm tw ir
\n gv fr ak o0 gb rd dv gu qf qg qh jg ux qj ph k0 oj wa jz bi ja eg c1 fe qn b5 rs rg b1 vo z7 us d5 r5 ii tu yh y1 or ek sl pm hy dc th sy ww ze vb wt m6 iv mj qe 6f qt gk tq ru yq au ap dr hh qy sf qa ik kt wd rz ej t3 ot ej ub wx oz th s7 t0 ag ga pv em fz sw o1 ip qh nd h5 et ho cu yz tq wq wn et tn yo si ov a1
\n ij rs rd qd pd qg qh z4 ql ip nq q5 xu bz lh o7 my 3w xe ws 2p w9 rk es er d8 pu y6 qc gl bv qa rv qt rb os ru fn qy qu hx or wa qs at zg mx xo bg yh ec os eb hd rw dw ip vw ki ok qx cu wb sn wm yj o3 tm ei ah
\n tr rd pq qd um qj u7 q3 cf db k4 gl mr gw c3 bs k8 vi 4v kz cg rz et ey tp fq y5 el gd dx qx hp mn cz wq xh m4 av t0 vz m6 qw tv rq ei il sb tk eg uq tc wo qo zs rd nx 2y fo j5 l0 l1 hy vy t3 t4 yt va y5 rg e7 uo ox at ir ys hd uf fx re rr ac kc cq qk cw h6 kp xn zu bd cz ca pn pq w1 rx vc w6 yo is fw ir ov
\n ra ij a3 qs qn jf qm nv cs cv kz q5 um q7 q8 km ya ys rx yn d8 sh pt fe se js ue rk m7 wp et ei qy to tz s4 af 3i lc wk ej hc ex t7 oc sm s0 tl fx re fb jr jp qc kc jr cc w3 yl oc ob ep
\n sa yb qn k8 lf d1 c3 wp vr wl yd iu kb sz g7 mn jm lz sd m3 lv qq j1 ex qo ry ru em pk i3 hi rf fk nc wd vu yt td sc tg s9 tz tx dh x9 qh ku dz my yr w3 oj se ei gz tq hx ah fe
\n w9 rl rc or fq a9 pp db gj hs lc qr ec p4 ph hb x1 ez u5 qx ea 6t tn
\n a3 qa dd qf qn qm qj vj wi ag wo ig e3 wz r6 d7 ax pe rb ey r9 is ot tq oy if hy se qx ar qb vd qw qe np xy nd wi in gj qu y9 ev ti tb qt px ud wo ll cy wd hw kh fp wk wg wh ym vo ub rd t7 iq yo ox eb yp ys au u6 rq ii io pe qh nz vl be n8 wv hk og rc er yu u0 rn yl is do eo
\n dd nn oc el yu tl rc rv r7 y2 hi qc qm wu cq qw xc kp tr fu ib zi qu wp vi ci qj nu zw t1 wl fh ev os f4 f6 f7 cd zc qx zy wu bs qn u0
\n o9 gr fu a7 qk xu q7 wp el yu fp ou y5 pm pp qm jm st op uc fx tn hl zs kp bq p7 hi ys qj ki qc qa n6 oj ey w6 yk si
\n q7 xo sr he uu sd s6 gy ws iz fk sw al v6 lq fh ie oh uz pu
\n of ch zj rk rx rc g8 i1 jk tv ul fi e1 ic sp in jl jv j7 nm rp r8 go hf wx tb oz tw it
\n se kc tj rx yh eh td pa zb qv c8 j5 ri eq b9 rm ik ev ul ti p6 en ok tn wp jm ws ke br wj rp en gd rq f6 ac ab zc rz ew tb ro
\n qd wr d6 i3 j1 ww if qt yn fd e4 qf j5 yh t8 u1 ev qc wv pw u7 oj ok yz tw o8
\n un pa jd qh qm dz pi z3 ny gs k0 wt xy z6 cj k5 gl bz d1 fq ye yr rh t7 ot if g6 im pa ps fk zw lx kr lv na wu vw eo cm te qo tr ec ty sb y0 i5 to ye so tc i5 sg ct qs sc ws qr xj 2u n5 rl cw dw ys qj yn qc y1 sl t9 ox sv s8 ya s0 tl ys rw tx fx ds rr cq cd ql qa au qg vg rx yt iq tn yl uz ei si r2 ob
\n rs gm qk gg m3 rj eq mv yf sk gx ve eh iv i7 n3 pb uf gh uj tg ox ww bg oz su o6
\n ih a3 uj rd qs df h0 jd d2 kj q2 ap wr ol nw bz q7 fq ir ra w0 eq ya r6 d6 eg ej pn py pp sr qb jb wq ni xe we lm be xo w2 qo mj qr hp tr qo qp ef of yw ai e2 i8 fb tm do dp i8 l4 wd p5 sn pf gr vs rx kz vh t2 wj ot ar t9 at ir dw qj nc cw fd sx qc mz lt pv br wv dr q3 yq vn ye yw dk oh rc w3 yt se ov ge
\n ds h0 jw he qh jr jt ql me na ah xa tf wt pj pk om 97 rc yg ym oe yj eg dl fe sz g9 lo qq qw wx rr c8 ns vq m7 xl gs vr qw qr kq qi qo eb tk ue dw e2 i8 i9 hy hh qt f1 pc vv qs bn ij i0 uj xh wk qz ns ej un oi yi rh od ha tl re tc o1 uh ac ip qj we qh lq eb w3 w4 ia oz eu ri uz ep
\n fr ij dl qk z3 qz wt z9 gq mr wo zz rd dd rz ee sw pp g0 sy vg ww iu pz uo cb t9 ld qr ei yx rw es ts zi wp wd gw wj hf r4 tt x8 wl t6 hc gp eb aj ai iu o2 nh qv ey kg dp wq f5 rt cg yw sr tb gj rb fm ro ah ig
\n ss ux q4 ji xa mj mi ld rl pj r4 rx yg ti ix a9 ig gj j1 ww ii qe j3 mz vl qq ye m8 b8 yl qp ik ki eg uq fi ok fb oq fm sf oe hp v4 nk wg mx kt vs j8 yn wc wj ot td wn iw os u4 tl u5 rq de io x9 dl ql n2 ji wb if hx
\n iy jk ql q4 wt kz fb q7 vy w8 ur ax uf ym yl py ou dl pm in sq ho j1 qr ls j6 ic sl ko xq jm qe qr qt yc es ry pf he i8 s3 pj tm oe qo lk wp j9 nb yd bu rs e5 yi ar rh ga ud al hh oe wx s6 do kv be pm w5 fn ey du do pu
\n po rd qs il rf uq of jf nf ih w8 b0 ur pl us ed tz tu ef rb sj tf ff i1 pa dv ue fk m2 qr wt j5 c0 vw xo b7 p7 qt zi wo gt qa oo qd bt nt zq x8 ou e5 u2 fj s0 yd sw re cd qx jp wc ja ga jt bh hm eq rv hx gx
\n iy ij pa gy qd qg he d2 qk d4 qz q5 nw wu am qm ft w7 rq yn ef oe pv ek fq sk y4 ts am fd qx q9 jf ju wy lm zw wp er sy qu oa ta jl ss gq fe wa p2 kq ws 2w dn xz t2 ej rp rs yy e6 iq ag e0 u5 tx dd pq fv jr qj ku oj ql wz fd s5 nj qx zt 2d qb nx pm ce f9 w3 er tw rp aj it
\n ss qa gm dm qk c1 jd k0 t8 mk rk tk om yn tz r7 px ac av ot ts if fw ez y6 se qb ha su qn cl wx we qr zt kr mj rc qo es tj ym iz tk i6 fa i7 s3 pl jx du qu j0 ws v2 wj ys yv wd s4 wf nm dt wv ub ez ta wx e0 fz al ap qg wr ar wb fj n4 cz qg wq fc mp yq ev se eu am gx dp te
\n a2 hv yb nv h7 jt lw xt lu wp yr rg 2o 93 uo pr ej ez jb lz ww az oq bk b5 wi qw rq hs te ea es ed fu ti uz fd tm jc do qi j9 zs j0 wd xv iz hr wx el ns oi t8 sc t9 sb fk hg cd rb mz wn 4i ov ln yr oz tm ro o8
\n iy pi jt kz st tm rh ya b2 om ef eh tp el in sc qc g0 ps zq nu pq j3 oe a7 ja js ng tc qe pp eo em fc s3 hh i9 jl qy i8 lk wa ae 1p vh ox rk em hf dd jt rn tq is oc o6 so pi
\n qs gm qn gw qb cx w8 ur yp up uy ek ez ar sy qb hd bx rl qt yi nw tt eb fl eh pg oh ib qy qi bp jz lf eo ph wh oy y5 om az tc ab wv wb kg ww eq ok aa uy w6 ag ig pi
\n ra a5 db co qn d4 bu qz kx me nr q8 my lp t8 gu rk yn et y1 ej g7 yq d0 j6 b6 qq rn kw ei yc uq e2 s3 oj s6 jl kf rl ny wg mw t2 co el yy ez eb e0 al qg km k3 n2 zr tk qb n5 n7 et tm ul
\n po uv a2 o0 rd hq dh hw a8 d4 wi z2 vt ww kb d7 pv tq fa ta dl oi y6 im ff ae qv sy si wq pq bn b3 lm b5 wi ku qu ru ul ri tx fb ss or sk wp qd w7 kh nn es hy wh rp um sx e6 rh rk pn sq rr hm dt ip dh pt wl h8 qc vj ly bq zn gd wn q4 hj yq xb mf ok tm ge
\n ub pa fy qf dh qj q4 wt mw cm k5 gw kb el w7 w8 mx ya ii dj dk gd dc gh st qb iu jk qr bz vz ab b5 mf pu qe xd nq eo yb pd i6 ue dq e1 qo wo sp 1o n1 4v at qf fi of xj rj dq ew nm x5 wh na ub e5 um sb ob em pb re ip x9 h7 zv xm bw 1v mp zr w3 xm ee yg rv rt ia is ro ep
\n cm bp hc rx y4 sr q9 jj rt qo uk ev to ff so bg eg y4 l0 go os ay tx qh hl qc wb
\n z3 nn o9 xf fs gd g8 ns ec p0 tb wf uv iw jt wr dq bj u7 e2
\n da td ta tw tf tt ay dq sf gi ae rl e1 gk af dp
\n un fi dx wt m5 vo ys j3 i5 ad nr wj mn tg ox bs ia
\n hv yv qa qf dg qj do ek w0 is sl ez sr i2 ww we rl vr qw y9 tu p5 uc hj i6 ud ws l5 qf xh kh lg wf wj uv tf t7 e7 dt qz ka xn cx xe fn it
\n iy yv rs qg uw oh q3 lr vq bz ab zm wa ds b1 w9 rl rz uy wy om uo ef fo py tw fe qx i2 qc qb qn ww vg ke wr j5 j6 oy qq ng sl qw mj yh xf yk xg qu te p2 ft y9 uk ym uq so fx ff fn qy du f1 na lh wo qo ge sk v1 wg mc dq wj iv 1h pk 3s ej oy ek td ex ae yi t9 go e8 rk tz ud rq ax hz dk qz kf wm yq cy w4 h6 rt ry tn r1 gz ux pi
\n d2 we aa cb o3 xi tu ti gd wq pl xg wc lm de e4 sj hc ic wc ra wc go o1 dd ip wl in wx js tv rx yi yc
\n ty um hq co ux ql q6 wi bc kn q0 r7 yz ib pm g7 po qv re we bh 8j ru xo ra eu ud qy uh ec ty ry yn hw sm e1 pg p0 dr qy oe lc x1 kt xz pl t4 el t5 ex sn us dq rq ao f8 pr md ql v7 v0 n7 kh vn wm u7 cz et w6 gl yo ei di tw
\n ub jg ph q1 q2 d4 q4 qz kl ld cn ji z9 ro ek gb w7 rh pk ea ax yj pv ot yl an sl y5 po im i1 zb fj qb i4 gl xq si m1 jj lb l2 ul sn ue s1 ta hg zu lh nd j9 ci qu wd bh ef ro ra aq t7 ex t8 od en fz fc df h7 qz n1 v9 zy 4a tc bb ea yw mf ia yp eo aj rp ob
\n o9 qa h9 dn vo a7 qj jt ji ne kc cj zh wo q0 w7 e5 ui vi ya wy c2 r6 ui px yh y2 to pr ab dj pn a9 pm tw sq ig hi bg ni ry lv wy ic bc li qe im dv rq xy ki i5 fa fv uu af do vv za l3 bi kd nx w8 nt lh hu ra ub un ec rj ua fk ir s0 f4 uf dw jt k2 kz ml cl km wv vv es tb yj w5 rn yo af ru ah ig
\n vg wr zh wo on ew ef ae ha id uf eg p9 ef gi al ng 16 rz o3
\n qs jw qh cv z4 ok wt k8 kg km wa uu us sj oy iv tw jm c9 fo nd 20 qw w3 yi re qo yv rr op qp ue oh s3 uu px jc hx wf v2 br ep wg pz t7 t9 au re zj kn xc bo kg 1v hb wv tt u9 gj yu ry iw dp o8
\n qd wi ij rh ef fe jm kw xj wh uk ef ti e2 j8 ou xo ny wh rp wj ub s7 pb nb qv ev o6 o7 yx
\n gr dc ft qs gm qd dn k6 lo k9 nb as zg bz lw ui ee g4 dl qv q9 lu jg rx w4 yj ep oo sv uq hq yw ao fc e3 ui dy du sk gc gy qs l7 kz ed ej wl un yu wm oc gq qk qc ks tk ti eq em ly vl er ry sy yo ro eo
\n lq d7 i4 7w y0 qt gw ch o6 eo
\n fr hb dc o0 yb hn gi jh sw kj we o1 vg nm q8 bz zk bf ml ev ed r8 iv ht fg th qv vz d3 ng xj 0h 42 ew vt yg qr qt ha qu hs qp ij yn eg of tl p8 fz oh iv jl ss dy zu or sk uj co kt rp wb wx fg ev t9 rj yp u5 us ys ak rw al io kc dt jr hl ln wl wz gy wy qv qb mu hd ky ku zp ww yw rl oh ee w4 yz
\n fu dg qf pg jg o1 dc by q4 st t3 lj ve jr am 2i rz ea lh pl ed pz y4 g8 i2 db g0 fj q9 qn bl en hr m8 qw rn qt yi ei yk qu xi uh fy yn ix uy gn jx f2 gr fi x2 zo pl vh ek sz u1 s7 ya em u5 da re f7 hl qh ju oz ar zb ci tk ob n7 vh og w1 ok er o5 ri ro tw rp it
\n gv ra fr ub h0 hm pf qj kk zf zh rj eq d7 oe eh ib oi gg i4 jd ph nu gc qw rr m3 vj ry is dk qi rm qy qu ep p3 ed pd ta s3 tc fd sa im ow jc oe qi j0 gt bm vm zf nj rg w7 x2 nr wf hi rp wk co t6 t7 e6 ag eb u3 e9 f4 om o2 dk h4 gq jo cr oz ka kx rn wn do ep wb vn ef rz ew yi r2 ro so ob
\n ft a4 qs pq iz pd u5 cs q3 qz ra rh w7 rk mv kv ee y1 to dj sj ta pn oi tf i2 th q0 vx vf ww 2l cb wt yq ku ye gs qe w4 qy qi xi tt es qp ed ef ti i7 tc pl jz ho zo qi za fy zy rk x2 r3 ht yv ex op ae iq u2 ag pb of dd h4 lq wx cy cu zy wm ry ef dj vx st ia ey te
\n rs al qd uq ga qj sw we pa bi ba e4 yy mo d6 er et ti rb py ek am ib fe y7 fh jv mn qe qr oe c0 l1 qi mh 44 xe ei ev hq ix e1 pg pj ui hp pm fr qs kd nk 1v wj fa wf yt t5 vp ex wx fh pn ug fc pq io gh dg oy nf v6 bt jo qz gu me wm n7 br tx mt q1 su eu di uz am if uc aj
\n da a6 q1 ph uv oj ji mp t5 mi rj cf jl w0 pk ew ii rv oe r9 ic id sl se su q9 vd we j3 ac d9 yw ew w3 y0 tk ao hr in e4 hu du qu jb wp cr qs v9 p5 vi xm kf s1 ea t2 wh y1 co iq yo au iy on ds fx yf qa zv qv f1 y8 wm u8 rc o3
\n iu r5 el dz rt m9 hb lc x2 zp aw uz
\n k0 px qe qr i2 yz qo ap t1 ou n4
\n qg q1 wr wt wu 5x ij rg lq eg ia r9 is dl aw g9 xx w2 qt au i7 us jc f2 ge qa gt l7 lb mc x3 3p tz u6 kx f8 fb ku ag hd oj o3 fn tw
\n ds rs k5 go qg ga qj gs by q3 xy q6 k5 4k o8 ws td mo w8 th ys eq pk yf r5 uo rb r9 td y8 tg ho qn gz li m0 oq kw qr g1 wy iv b7 vt qr qu ti to ta ut sa i0 pl oq sd ho qa gy qq l4 ks fu wg qg kj eh ez yu tf s7 os s9 ya em pq tc fv qg ve sx af ci ah qj bj df ry rl wm zy tv ol ey ox ri ie tq ir yc
\n ak ra yb ds gt fy qh d3 ql jk jl ni zs q5 zf lf so wo mu yt wa w8 kl ue e7 2d mb yn tu ac pv id pm sq sw jo dv jd jg qq qw qe wr j5 wu 1h b6 vr yf cx lz rn ho gh qi es ev ty p7 fx fs s5 pl sf lh sh i8 qa xs 1o kq zg qh wk fs vo wl ez iq uo tj u3 gs ii je jr hk ql xx 1j v8 nz kf vz ww yw yt w4 rb ol o4 rn ux ig sp gc
\n yv fr qa rd gm ps jd a8 qh ls vg q5 lg eh z0 vt mi vy rg lp ex ew d6 yg rv oe fs sz g6 sy ha cx qq wy j6 dk hr l1 qe gl ex ln uk sv ty at ru uc ts hi hl lg jv qi vc m0 fy xg qg eo hf mu mo kz ot np oy na el yy wz fh gp up ir e9 s9 f4 gf pw uh uj jr ab qh uc wl ce qz h8 v9 wv ie 37 eu gf yv 1m ma yw wm oh dk sr oc ei o8
\n qn cd zf y4 oi dv xq q0 lc av cw ki xd lx qi gn bh em uf we ja ox iw qb wn my zs y9 ux
\n qd qf we ls lf k4 eg bc e5 rl ea r4 oq er ip g2 yl ot iv ps gx qr wy xj vz xl bx 3o qr eu qi uj p7 uc ph in pk qt i4 gq wp v6 kw kd xk zw 11 yj wj rd oz th yo eb ya tl au tx qj wl dz wz cg zv qa rb wm 7a zs vj yw ee eo
\n jd go qg d2 ji qn wa bf t8 ys eq ui d6 ed yn r7 is qb q9 lp lz qe c0 wu tx wa te qp 64 uq in qt qy wp j0 lz l5 og ca sz un ec rh pb pw h2 kv aw wy qf 16 rw ew tb aj
\n a2 gr qs fu db qn q1 uc jr qk cn q6 b2 ne lg q7 q8 wi wp b1 ec rk yj pc fo iv sk gk jb qm zw m1 wx zt xy wy em 41 ee gh xg cn yv qp sn od ao pj fs ut s5 tb ad jc j9 xa uj ws kf wg vp nv fa wk mq x6 vh wv t4 ex iq 7r y6 sv ox ev eb rj rk em aj pq gh f8 th os sb mt ak q1 xr yw ti ee tb as ox o5 yo gx uc
\n qj lp z0 aj wp vr wa bb xt w9 ya on ew ym ia ix pt tw dz jo ae cc qe lc qr cn b3 c0 ib ml qi uj qp pf p8 e1 s3 tn ui sg pn i8 hb ij qw pd ld fo ap ty ro 3b r0 sz ie gp rj e9 fk gd pw rr uj cf qz zr rq 4p kp pr vj w5 iq ey rn ie eo ir pi
\n gr rs gy pw qd ga jj z3 kj ql nn bg dm zz uy pl e0 lh ef oe am y5 fd qx hi uw i4 q9 hs jb vd cx ni qw wx zt qr d0 wi 43 w3 cc b9 qa rw oa ev ry p4 en tk ti yq pd i5 og ic ye so tc de pj ff hl oe sj qs wf v9 xn gm wg xm 1f ph dr vg wk ns t6 um oa e8 sb t0 gs sm fx o1 de h1 uk qh zj zk ng ct gp nx xe 3z wm rz yk tn ro
\n qa pt k7 og kl wy rp hx wp wa ui mx eb 95 ac eg dj yz aq in ih i2 q0 cz cb dg xi cq jc qe qt es ed sb en iz fp ta fc tv tn gw ka i0 lz sd il qf 1s iz qf nc xj xk ep r7 rp gu t7 wc t0 en tl iy iu pw kn km ql ct qp ch fl wm n6 rw eo qm vx ty ee ru ig
\n um rf qd db qf od d1 mb u0 le xu wy q6 mt bc qw cm uu us r5 uf or tq ek sx i1 it la cb ax t0 wu ab 1t qq g6 ko g7 mk qr ey ha ea qp y0 en ue tv ho i6 i8 sp xs qf v9 jl kt rk qy ot 14 na ub aq op yo en tk ob on tx f0 qk jp vi iw tj x9 zi n6 wo wb se aa ag oc gx
\n iy ub gy pt pd qf me xp w7 rj tk r4 rx ui ii r7 us pb pt g5 fw gf dm wi w1 eu re tq oo es pd ri tl og s2 fx ap ok i4 di lh f2 1i vm cp bh wj wx of on tx dt h2 hl qk wq qz lr tl f3 ce kp yr yg ro yx
\n k6 qf cp la wp gv es pl uo eg am tf y7 i3 hd jk we d7 rl b8 gg ug es rt p5 eg em tz ow 3y eo wg t1 lc wk ol tj en ak fc f6 df gt ol qc rn tz wv rx di ov
\n pp qd iz qm vk jg r4 pl ym y7 sc qn jf qy rq p8 yr di qu hb wd rf ks gw qg s1 x7 ec ae iw eb ai sq v8 h8 le ea vh yw yp
\n ik a7 cp sq q1 lq ql wa qz lr zh rp ra gb w9 ys ui ym px up r9 pr ek qv qb hs bg wt ku pu dc p1 qo ik uk y9 y0 en hr tx ts pk jl ce lj l5 p6 v4 wk nu vg oy aq aw rg os az uj kc py ql oj qc pc fj jr bf cx es vn q4 y0 og w2 ue u8 is ag ie yc
\n rs dd ik k5 hm dg k7 go q1 qk wt q7 wi ws t6 k0 go ii ee io ym ey sl sz sw jg si d3 qq qw nh lp cc kw xt m3 ip ln nf zm qq tc ex ry at iz p7 ux of he og dq e1 i7 pj sp s4 ok qt gt sd xf ow qr pd hd wj qh x3 yb lx wx um e6 t8 s7 uo u2 it sw pm rr qg h3 aq ze h8 ks zb kb bh ec wb vb w2 oj af
\n ak ds dh jg cp ws q5 nq wy su q7 kb o7 ys sf et r9 ta sq y6 dn sy cx na j6 jc qi qw qe qr rb tn 3g eo uh tr ft ri uw of i5 ue ta fs s3 uy as ss qu ns lj wp zf wg sm x1 ix mc va mi rx ej yy y4 t7 ex u1 y6 u3 up en au ds ap kv qh kn gw k1 zv eu lu kh tx qk dr dh wm ti h5 o4 w6 yk af fq so aj
\n uv sa hb ps q4 as wi ej qm zc yd yn fp y3 td hy ue qw qy es tu uq tx e3 jz ud sv l6 fu xh dq wk wx yi dj qz v0 qd ga mp wm yy tn fn yx
\n qh qz ar qq ma kq rx qa st ei
\n dc df il he c1 jt qn yd yn pe et pn pi d7 ke g2 j6 rl sk ng z0 m8 mh qw j1 eu qu rr es ec uk ev ul pf e4 sg jv m9 qf vd wk gu rh e9 f3 on qv vj dh aa ru ux yx o8 a1
\n ra qs h0 qh bf q3 dv bl mr if ws df ev b2 pl om tz ax yk ta y7 aw dn zr ax qt m5 xx wp qy qi qo at ti p7 tv i0 fm qu sh so lk qp hb p5 xk ib vd hk t2 np ek yt um u1 ir sm yf ug az qj v5 wr fg zv af qv ck ay cs ww pq wn w1 yh as yk ei
\n tr yb df um qf iz k7 q3 we cb cj ne zg a2 e6 ya r3 ut on rq io ow qx ja qv cx cv bh vj qr lv pc 3a rm ep uk ed ev au p8 so fx p0 ts e4 fb hj qt dy px sf f1 zo vx qa wa sa qs vm wf xg kf fz r3 bu t1 tu ez t7 va e6 fl tz uf gg io qg qj h5 zz nh qz zt et ba lu tq vz xe bb md u7 oh 5k rv rt tb yu tn ah
\n gr da ty qj by we ls av kc qc wi wo xr mx cm yg oe xs pr ua pt dk oy hp qm qq zw vk xi ln he rx ko dz yt qe tv eu qi yz tt y9 ev ry ym ay uq pg oj aa s4 sg hp f1 qu wp qa bn vi os iz hw kt t2 rs wl r0 ez rf pv hs om dd f8 uj dj pt dk km k1 qz qx wc n3 nl wv qn zo vx ww dr yr oj r1 tq
\n ra jg jr ao c1 wh rj fp gz iy lo gc dh qw qr 8p eo ev fu tl i5 uy uu ui qp mb hk yt ou aq oi e9 ip dt k2 qx vb mf id
\n rd h0 qn ql la vg qz lw q8 ra sp ts pr av qc vs vg ku am z0 lo ry ev eh i8 aa pl dt du i4 zs w7 wj xl yg yh ra ex u4 pn lw gu pc on n9 n0 wm em tn
\n gb ik rd ql xi bd yr e3 qq w7 ex rz on ui yg ax fo pv ab ta jp qw xi wi qw qe fh mz eo gk qu uj ed ev en fo ux ye fv jv ws lx kr kf n5 qj ea s4 vh ez um tj ir od ga tk f5 dh uk pr pt in v9 js sv qb zn wb vl zj wm ca mu zs ef rl yw u8 er id uz ah
\n iy ij ub qs lo ql jk dv h0 wy cm q7 wu eh fq w8 hm w9 mv yd rz rx rv r9 eh pr ek dk el hi qc sy i4 qq lp jj we m2 g2 fo j5 wy m6 ve tx yg w3 rv rn rq qy hs tt y9 ry ym eh to e1 ur ff hk do wa kq jw p7 yp ky r2 wx oy uv ra yt t6 yy sz t7 wc s0 of ds om kx ng ql qz vj wt wb ly wm lw dh md ew w3 tv er as yu an gz si ro do
\n o9 k7 q2 dt 1i wa uu t8 ut mv ef uo g3 gj hp jn nt cm rj ms wi b6 im qu eo yc ex qp eg sf do sh i8 ih qa wa wf kd yo xj ql wf ek un wx t7 s8 rj f9 qh qk k1 lq h7 in nj um bu qv ov n7 bh bn 3z w1 yt et o4 gl
\n a2 gt rs ty rd rf qd qn jf qh k8 q1 qj ql d4 cg wt q5 z7 lr wf wu q7 sd yg yh g1 eg to el ih sw tf fg qx dv q0 wq qq uu px vl xi js jd ze la ud qy rr ky ft i5 em p8 p9 i8 hg im as jz tn qo ul wg 8d vs ap mq x6 no t3 ub wl tf iw rh ox ua pv ir us pb tx pw dg h1 uk ux cr sz ko wx jw vl rc tv af du ei
\n gm we cm jx lf vq vw kb wk e3 df r3 r4 ew yf ti id fe fr su xr sl jg rq rw uq tp ss qy ws od nv wg ro t7 ar th ak da yf sw io jt cq v9 kb iy u9
\n qd ga q1 h8 xt um wt nq wy wp a2 rg w7 hm cf tj ut r3 ch oe r8 pa qb jb zw mm wq pl m2 wr wy mh hi ei qy nw uf yv s1 fx ut sa tb ss hl qu qi zp nf zd ar l5 5h gm vo ix xk wk wf vf el r0 sx e6 uo rj f3 em dd uh qj cf wz n9 ga tc qk mu rt ye w4 o4 ad ag
\n qa jr kz c3 c6 vp e0 ng wu ug ty uk tu to hr sp ud m0 ar pa qf wf kr fi ya kk wl xs ed mp x6 ub gu fh rj e9 ya om wl vj ha ex y0 id
\n qm q2 oh cd q7 kk ld ys yd rv yk id wt qy iz ri fi i5 ic e1 ht 5z iq ha ai sq pn al gh un kt wq mi dr ax u8 u9 gk ru ov hc ep
\n iy sa un h9 rf fi he uc u6 cd q6 wu zl zz rk lf yd rx d7 ef er rb d9 r9 im hu zv ps qb jf qm m8 qq ji g2 kt qq ew la xy qo es ft ik tl ye ur as tb m9 i8 qa ka qs bm zg ix ya kl t1 wj r9 oi um aw yo ie ys yf hg gq nh zc sb nw qf xm bc xr bj es rx w3 yj iq tm di gx o7 pi aj sp
\n il qf pd k6 h0 na is q8 4p zl jl z5 hm ec io sf dk if gd qw 1a ld lf qr yx re tq y9 pd iz yw sa wp bn jq w6 v3 x2 br ta yi ha en o1 io ip pr kp nl lt kd eu kf kn n8 zs rx ux
\n ih db gm jd wr zj xp vp qb c8 pc g7 uf uz p7 sh or xh xm wh mt no fh dh wv tk li qm vb ms if
\n he ql wi bn c1 rc ip ia av or y8 mx yr dx ex gz 1p ic wf aj kn 51 bj wn o6
\n hb ty dv gu ps qj ls qz ch q8 zh xp bs vt rh oe ot pb y5 y6 fr ih sc q0 re zx lm id xp yy qr ry ay p6 he dq s4 ff qt sd vx jb qo qp gb ws wd sd co fp kg s1 nm rp cu 8l y2 tf ev sn au us fz hj qg wc u4 au qh wv bn eq r1
\n uw vr eq rx et rb fa ek id qx ui kr wn uf p4 tl au hw tx im sf yd dz bo wb xw
\n uv yb ik qd gm gp k8 qk ao z6 ps mw zf jc eg a1 wa 7c zz rh yi lf pl r7 yh d8 g2 r0 tq su cz pl qe qe wr wv ku ho qt yv uj ij es ec ik yn ym uw tl sm he p8 fa ho wo gy ws zf bw nb 5q ql t1 ro rp ej xg uv el l8 rd wz rg go rh sv fh ya it pn hd ao az tc dr ac dy ot sj nd qz ok um ol sx xb wb wi n8 ji rz yr sr h6 et o3 ru rm pi
\n rs fi ag c3 lw ys ef sg qu qi uq eh e4 gy qt ya ro hx oa f5 1j qa cl wq rl yh pu
\n ub rd qd fi jl zk oq r8 y1 tp sl i2 qn sd cq 6d mj w3 p6 ta fm bo nv qi wh yj e0 ao uh kn h6 r2
\n pa q1 fm c4 ig ex 2a yi mx ek ez dv jf qw qe 4s xt ld dh qq mg qr yc eh s4 hj yy s9 pv rr uj or qj cd wc ly x0 wv hh ye ew yh rb yk o5 tm
\n pp q3 mw rd up td j2 lv af ih hb ee xh yy ua ug aa tb
\n sa tr ds az qd fi dn hw qg dh qh nt z3 qz ad q7 q8 tf vu ue mx vp lg tz er yj to hy fr sw th qn hf gx jj pz wt lb cm m7 wi b7 vr lo yl qi ry ef sn uq ri fx oh i3 sd i4 ho vb wa qa ik uk ar hw l8 ya cw s4 wg r7 ot wk gu u1 fh th rk en sm u5 iy iu re pr hk qg kn gq cf h8 nj ct gp wb qg hj wm cy ok er tv u0 sy fq o6 gx eo sp ob
\n yv ak ra co wy zj e7 ew tl fo ek ez im q0 jm bj lc tc rm ec ou bn sd os x2 lh wj ot oi y6 e6 yp ob sq p0 js qh el bg rr rc xw pu
\n ih um k9 q4 ls jx ej om sf uh dz oi qx cl zm qw qr zc qe i2 i6 uu qp wp ws qd sd fj mx qk yn wj ub gu ar pn rr qg ln dl al vg mf w6
\n tr ub ds jd gp qk jk d4 kv xo ws gi yo sj sl el tw i3 ow qe zx nx b4 qq ee eu uf uh ex p2 rr ea ry ef eb y0 en ri eh e1 oh fx fv sj jn xf qd vi l7 wg x7 r9 uv ek yt ns aw sx sc vf tk ud ds o2 pr kv ab gt v6 un qz wr wv rb os ie u4 rm zm rw n8 vc za q3 zu yy o3 yi ag pu
\n ij a4 uj gg jy dt 1w rj a6 r3 ii pe r0 ej ta ts ff i2 ho ov wq kw qt ot m7 qq xv ei lv kr yx yb ri fa ur de pj hi si jq wg r4 x5 hj oy 5i u3 tx tc nd v6 oz wc qv bz qb qj zl dg ed ka vh w3 yt ey w6
\n o9 ft az ps hq uq a8 ql we wg z8 ye wk bf rs wa c6 dd ys rl wy om pe ix y3 g4 dz gf se tg pa va jn jj al qw sf ma j5 qy wu xo dc rn se eu xb nw qu qi p4 ef ru sm eh im ad gm jv pm zd g0 wg qf ai qz ym qc t8 op iw ox ay tc av k1 ko vj qb zo wq bg q2 n0 rl yt as rn uz
\n o0 jq qf he qh 7k q7 kb wp z5 tl ew yg et or ez jp g9 jv gk lx vk vw qa qo ou qg kk xz rx ro wc oa us ip x0 ku hp jr o3
\n pp dc gi fi qf ql by we la za un qz q4 jc zh wp kg o9 qm bm wt r6 rw eg ix yl tp am a0 aw i2 fh th xq gc eq xx yr qt xt nw qu ri uq tl ue pj p0 hk vx uf jm kq ws jl 1s p6 ca he x2 wk wd r5 wg bi hk ro wj t5 sx fh sv e9 ya aj e0 pn ao ug ac kn h4 gq nj wr cu qs kf vx xw k1 og yt u0 yk di gx dp
\n ij tt ss dv a6 uz gp qk cv lq ql un kz gj wt 4y fq lh z0 6h w8 vp r5 ee tu sg pv y4 pu a0 tg q9 gx qq qw we la wx rr ls zy qu wi xz wi xp ew qe rn ei qo uj rt fy ik tr tk sn pf i5 tc sp s4 in gv i0 f1 si ks nl kw bq co mu eh oi ec sc wc u3 ga fk sm om kb wx bo zj hv y0 en og q6 er tv tb rb u0 w6 tm
\n rd ik og q3 q5 cn xp c4 ig mi e0 rc ym id tq ou po gg qb sy ob hf pk xr qr up j6 ng xx b0 qt tm eo vw ux yw pg tc e4 gy p4 xv pd wg n5 r3 wk iv rl ht oy uv ub wc ar t9 ga s0 em pw x0 pt bw wm vv vm yg fn ad af do
\n hb gg kl q5 t1 mi a4 b9 r4 ee up pr g8 gl q0 cc kr c9 vq yy wa qy mz ty yn yq ai og tx tn nd ws 1d ky x3 sz td gu t8 op gs tz de av sk cy zm be wv qk og uc
\n ph qj d2 cd q3 q4 bi wp vt oq y1 ps cw kn gz ij p4 sp e4 wa sx nj v1 w7 me s7 e9 tc k1 lw zc vj wb kb tw a1 aj
\n rd a5 hq qg qh q1 la cd kl mp k8 mj vy zz yu ut uo sg yz hi gk sy q0 m1 qw b5 wi dz qw rc aw eu zr uk ti em yr lk kw nz wg fx tt wg x7 rr lm ju th tt eq oc o6 yx ro o8 a1
\n a2 dm wy ej rh rg pe a9 oi y7 zt vk ga yf pp fh ml tj p5 sn tk im jv v6 lb pf zq ty wh t5 go sv e8 it f7 ac p9 cu bw kg qk f7 w2 ee w5 tq ep
\n vs rg rj 70 ys nq uf ex hh jn kg ep e0
\n df q3 u9 4j bn rj hw up td i1 dc hi zb wv g3 l1 rz qt qy ty tj ef eg sm tx ap in px af mx r3 r6 t2 t4 rd uo e0 iy ii hh qg zc v8 qc ch px zy zi ye og er tm if
\n gr pq se pp qe lq n1 cy qb wb ey
\n gn q1 ji sc nh pe yh qt ss rw hf kx zm o6 gx
\n ih hq ap bl wi wa 3y er pc eh r0 yl ta ts dl gg fg i3 hp kw lc ls rl ff wg qi uh uk yq yw ok as wp gc jm qf to yy fh e9 pn qk sz nm li q1 vb yq 2e rl tv sy di sp
\n o0 yb rf k6 qm fv q5 wi rp fe dd mz rz ee oe ia yz am ig hp fj su gl li we m2 ls xj md z0 uh yx eb eh ur i8 qy oe i5 jv ce jb jm lx ci kg oy hx e5 u2 tj kv qh qo af pv tz az vg yz ri ge
\n il z2 d2 cs ba wa 1o ys uy ed er d8 sh qx sr qb jd ov xq nu lv g1 ku pu rp qq ee et rm eu xh i5 p8 tx in i3 bn v3 ap r5 oy sl oo dr dg hj lm sk ff vu cu wb kd zi wv mu w3 yt ok rv ol ey yx ah
\n hv qs qf qm ph o2 zg wa a2 rl pz ow uo y2 ey ix us d0 ek fg ww j4 wv a7 wu qq bv te tr uj tp ue ts do qo cy uk j5 ra ou aq iq ev tj u5 tx gg df dg h4 qj nc wz v7 im kv jt y8 rz w2 yy ei ge
\n gt df jg og k0 lq kz zf iw kh gv cn ur ea eq yj ix id ez dc qc st wq c0 lm fa lj qq mj sw qe xb rq qo yn ru at e1 p9 s2 ts i8 s5 i3 sf jc xa hb qq gy wd p5 ow qf wj 1n wd qz yt ex e5 op at sm ud tz yf tc ax f7 dg qg ve sl qv tl wn 2h ky yv f8 rt o8
\n yv dh kj jl wr bi kc 4j nt c5 lo z3 e8 on or g3 g8 uq sr qm hd ll c0 nh ws qu ug ph tc dy oq qo nj wn t7 ox zj qj km h6 ql zz qz qc hn bk fw ob
\n un qs fu k7 co je gp o1 bg dt vu rj mc rz rx r6 g3 ek qc uw fj li qw lx ku z9 br b6 mz yc ty tl yw yr e4 i3 ud ce i9 rf bq xh ep wf ej rp vu wl u1 os ay of pm ap gg dt hk ab ql lq qs lt nl u3 wq n7 bm ef xm yh w6 ru fq tw
\n qn ql zj rg ed hs we qw re p7 p8 yr tv pc lh gx i8 wp lz cp fv lq 1b di
\n rs q0 is q0 yq rl qq vr qa v3 tu in h7 zy u9 o7
\n ak pw af z0 wa jh tf to ey r0 ta fe tf th wt m5 xu wu xo l1 b7 bv se w4 qi es dy ge vm l8 nv kh l0 j9 bi ez iw ux f0 gt zc 6w bf cx u8 w6 yo o6 fw
\n gb py gp pg ql um lf bx 6q ra w7 vu rj ui pj tl ii y1 r8 ac eg yl sj tp y3 py im tg zv ll ip wt av fq qw dc yu ei uf qu tw y9 em tl dq fx hk oq zu jx hb i9 bq iz mv wd qi pz lx ek aw fg ag u4 ir tk of pb az f7 qk wz le qs wy wn dd bj mp yq zb cj rc tm yo
\n o0 pa rd qf dn qg nm ji q6 cm wp ec uu ax r8 us aq se lt g1 bl vz jc mj 6h ul en yw e1 ye tc i0 do ge gn v2 r1 7f ed x8 rf oo t8 s9 u5 pb fc ug ab pt uc ce qz h8 u3 ga fl bc yq iq iw id o6 r2 te
\n da qf qn nm wr jw c2 ig rj vi ys pl ed ii ax y1 ua r9 ia ab tq an ek dl sl jo g8 qv gl hs jf d5 c7 kt ix wy wi lm qq dz ko qe ff pp qt eu wd yx ec p3 rt ty ik p6 pg i6 e3 sa fd dr gb jl as hz qo j9 j0 qs qd wf p7 br wd x5 hh t2 wl el rf tg ar rj tk em ud pn rw av iq nl qv x8 ov wq wb ec vm y0 w2 ew 5k w6 fw a1
\n a4 a5 z1 hr qk q3 mq zf qc wu q8 bd a3 xf es yn rc et d8 pr or yl fa se dv dn uy vz wu en mb qa tj uq i6 pl du jx f1 xs qs qd vm kd wh kh mv ed rp ra co tg oa u2 ie ha ir da pr cq uv oy qb qb eo ye rz rc ei o6 id if do yc
\n a3 qs w0 po gx xo nh uj e1 lz os wj uv ud tx rr gq ve ch cs xz
\n rs rd qs qd hw dh q1 ql dy mj zz ws vy pj ea mv yf om uf sg up ix pb ab ej tq fr tg i3 nq dn gc wx wc qr wr lv t0 cm wy wu ko qe qr ze ug oa ed ym p5 os uq i5 tp pg s1 ok tb fb vx ns p1 wa qs ka qd lx ps sf zy kj pl ro rs rd wz tf ev gp e8 u3 ir od f5 dh qj cw qk h6 ql ad qb fj tl al zs h2 rl eq rx er w5 eu yz id pu hc te
\n pa pw q0 j4 4f vq iv yu ry fa e4 kw xm wj t3 ff ye w1 oj rv ul ep
\n mw di ec wt rx ko i4 qo l3 iq iw py yl
\n dx uv yv qa a3 ij ps qh cg qc 2n mc ut eq rz ea kc pz sj dl in db fk su qm qe m2 we 1q ke bn xy wb yq qq vr qw xc gf tn re oo qo p8 iv e2 tc e3 e4 fv ff pl sd qy qi si pm ws aa zw bu hu fn yj pv hs gh o2 dy ln v5 qv zb tk bq 4p qj zz wv 8n h1 y9 wm yw og mf u7 tq ov sp
\n iy wt gx nd t5 r0 yl tg dc qc th wt ld nd zn tc ke qu qo qp tk fa hh gn qp 5d qd ar rg oz fh at ag 2a kd nx w2 w5
\n il wp ee ym tu ef dl el qb cl qn ob qe qr m6 mf xq i2 ud fn pc gw m9 l7 fs eh gi e7 fk ys pe ok 30 mf o8 uc
\n db k5 a7 je kj q3 we wt q6 ie ck kg kn gr lf pj io us id in aq jo qx su hd qm li qq jm we lc wc ld d8 lb xj em rn j4 tq oo eg fl sm he ue so hg ok dt qt px j7 qi nz l9 lj x8 wj na wk ez ex rg e7 u5 h4 kn ad cu oa qd do q4 eq w3 yy sy su r1 ri if yc uc
\n fr ij ft db dn gp qh ph ga gg kl ws wt ab q8 lk wp mi w9 tj pk yf ew us yz id gd y7 dc qx hp qv gl jn q0 d4 qw re qe cn cq pt wu lm dk cz yy qr rq te qi rr ea ex ye dq ok qt sg qa nk wh kt bt bd lh wf yb hu mw el rd rg sb em on qg wq oj zx wv n4 bo qf bf re wb ev yq cl yu w5 ad ag id a1 gc
\n qd ga dt ej kn r5 ax sg ix av am pu g5 ez fe qm nu ii zt wr vm qt jd im rb ml yx yv ru hr gv ad dw x4 ub hx wz rd ol oo rg yf f6 ii uk dl wl yw yu ie id
\n yv ga z3 kk pp nq le lr bp qc t1 c2 hc vt lw jl w9 ur r3 ys iu es rc ud yj r9 pb ot ta sk gj jm gx xg sf lc qr ht ve sl g7 qy nw vw fo ta tb oq fn qu hc qp op nz ow wk zo zq yg wg ke yt kr yy sb tk rk tz iu o1 rr pr qh dl dz p0 n2 ci cd vn ms aa yh ry w6 af du gc
\n q1 q2 mw bz k6 xf ec r4 rx yg ed d8 pv is sj qc q9 zw vf gc cv d0 xx rn ex uj ij ts ff ge i8 zg 4m x5 vh oy yy y5 wc tg at fk ob fv f9 ql bo k4 zn be ww ea ry tv w4 ru ov
\n tu g2 tq od pk tm fi y1 uf ku wn ew
\n hn rd um qg qh ph aa zn bc gv w8 rj ea es ui r7 r8 ey fw gh jf qn nu cc la 3u bx ve po et ei eo ea qp ul i7 uu fb dt pz qt m0 zf l8 kl t1 ej wj oy rf th rk gs sm em ap hg o2 ub wx ka hd br q2 hg y8 2e eq tb an oc it ep
\n tt um gi qh lp kl lw q4 q6 ro b1 if y6 qc th g0 q9 qm j2 we xt xi nd is ng bc ku yw sm ye dw e2 f1 lg qo wp zs gy l4 ul lv w9 br xl ql vf as yg y3 t5 wc ec iy hf ii f6 re hk qz jp oo qx xv v0 f2 vx cd vb yq ew zu yx
\n lo dt mt z3 rg av us pa xq wq qe qt yx y9 ev ry tk hu oe oi r1 x7 wk wz td jy ww qc 15 ba hd mo 72
\n rs wi vt rh we jl ur tz tw ht y8 fh i3 qb qm b3 qy ep op yn tu ay hw fd ug qp qd x7 rs yy td u1 t9 sm uh dz ql 4t rw kn wn rc eo
\n a4 d3 kk q5 q6 wf wg m4 2b vt w7 ur uo rw pc g2 sl if y8 fj va rr ld wn xo qw rn ml la qu ep re rr qo pd eg og e1 i8 ui qt px i4 jc gq oe jj ws ks qd ul zh sm ql tf go e8 os tk rk ay us u6 dw dh pr qh qj oy lm jo cf ff wm br k1 en og aa rb yj o4 te
\n gb dd rd qd a6 qj wt jx z7 xi q7 kv mv uy pk or yk ek el y5 ez aq dx qx th sy jn pe vl fp xz m8 ng jh kq qr la ei ft p9 oh hj tn ho ud xs wa jq vn il zg p7 fp ic qk wd bu e7 go hd sq ug hh ip sl ch qc px wb hs rq qh bk rl ef yw dj yt u9 st yi uz uc a1
\n al tt pq um uq un z6 wa vu w8 ed sj r0 tq pu dz qx js 2j na ip vz lm qq qe yj ud ei wf qi te p1 tu il tk iv dy i0 xg sf ix gp fk ai qk lm ql ch fg qv vg yw rl yy rv rm pu uc
\n rd ik qd jd ph gs k0 q3 qz ia q6 af q7 6w o8 tg gi sj ou aq po ja qn q0 qq gc nh xt wr fs m8 qw aq wp ho qu rm uh tr qp tj ay fi ao i8 aa i9 tv gv qu wo lj vv wa jm qa qd uk nl g0 qd dn gw ic kj nn wf wg dy ej rp y3 rs yy e6 wc oz fh eb e0 of hd yf uf ab gw qc ww yc cd ji y7 n0 wn cg u0 rn yp ie a1 dp o8
\n ih h9 qd xy yl ez g9 lt qm on vg rz dx qa kt p1 ex yx od uz e3 in sd oi qa cq dr me e8 ua ya dk wx bw 4p vx tn o7 pu
\n ij rf fu jt gs ld wu q7 wo 2u bd k8 rg ws gi oe yl if sq hi dn jn qw bb cb wt lm sq vr qe wd qi qo tk uc hp i0 pa w6 fo v4 1m x3 t7 u5 sq ai hg ap hm ju fl tz wv w4 ry tn yl fm gl ox
\n ql db ch wu rl ih qc 25 pb qq ty yw fp ao qy sp p1 qa rf iu rw qg uk gq km dz wv at qb jo eq ur rb ad
\n ra fy gm iz qk qz lf bp kc is nr ws mb es tl d6 up pe ey sj fq ek iv pn sx ly qb jd jn q0 lp m2 xl fs xx rs b8 hu rn ey qo yn vr yq ti ai e1 so ts jz du do oi sa i0 oq ow 3r 1a wg qg r4 yv ty r8 wk wl s7 u4 of e0 gd pm yf h1 qg n1 lt sv qb eu wi mt ez w2 yk su dp
\n rd qf dz aa bo pd pm qw rn tf ah wu
\n dv jd he jt ao ql zg eq pz oe dj fw sc fj nt io fi lr xu ns mx qe qt ei rq qy yw em e1 i8 sp in jv wo ci fa x6 fn ej wv oi um yi oc au yf mt te az yv yi ad yo yz
\n uv hv ty qd nv jh qj q1 ql we fv vj q7 gw fe wl vu w8 vp 6k yd r7 yj ia ey ot g6 dx tf im hu ae qx fh g9 lt fk hs su ov lo m1 cn t9 wi ki qq qw xx aw nw es yb vw yn ry pd ix em so ut oj du f2 lj qp jm w5 xh ny wg 13 wc qc ek el sx oo th uo yo at pv hn uh hj qj zk ql lw zx qv cc wv en yr w3 yy st uy iq ox an ah
\n rs pa df il iz qm k9 bu jz bi ji du lo ts yy xf cv gu mc ii rc up d8 ey pv av to yz y5 fd qx sc qv jv qr xt bk m6 ot md qq qt rq ex p2 yn sb xc fp pj qt ce wp i8 j0 wa qd gn ps qf be qt wh ky l9 za wg rp wn fh rh em vn dh vw ng wz k2 h8 f1 nz zh yz q5 zy e1 yh ad tw ep te
\n db wr a1 pk ew uu r7 d0 pn fe g8 la qq b0 ef os oh pk fm wa wk yi ev ua sq wu n5 tw xc er
\n fr yb qa hm d2 q2 o1 oj ox dm oc km kj r5 px rb et r9 y7 vs pj q0 4s lm gs qe eu ep ti tl of jq od pf nr nb ea ej yy rh u2 iu qh dk zl qv xe 2k vc vb zb xn yg gk ox tw
\n hb dc rd fu gu zd js wu xa c5 a5 w9 vo r5 d0 g3 oy ib i3 ha jm nu rr wy m8 b9 ws qu ru eg uz hw eh hg sp sf do jb zp dp nz wj wv fg ae ah ob hd dd gh dz qz xc s5 vi je n4 re bh ma wm as st tn yl
\n wi w7 om ug qa x4 yn r7 gi re n3 n7
\n ij pa qd qg uw qm d4 z4 q8 t3 mu yp uf ia pm ez ih qn jm nu kw qr j6 qw bb yu qs rw wf re qi ru jz tm gw lh ce xa wa xs bn nl ys t1 wk r9 gu oc u4 hd ku hp yg rv as yz ro dp
\n qa ss dc gu qk cs cv fv nm z7 lf z8 xs a3 rk r5 ys eg y1 po qv cl jf xq vh we wr qr b3 bv yr wf re qo sv tj ti eg dq ic fx jz du qp vb ws nl wd wj zq vg t2 zf wb tf yu ex yi yp tj en ua ud dr pe qk dl lq qz h7 ol v8 cy vi wv ck el gd q2 yw w1 ye xq w5 gk o6 ob
\n k5 hr jk ju k3 jq q6 zg wi id bb wa gb rf rq g1 et ot pn ht dc ww c7 we c8 cm xo mg w1 1l yx tr p2 oo lm ry ao e2 i9 e4 hi tn di lv cp ca 2u t2 no ub ex rk ys pw qg av py ql qo lb pn eo wb er tb yk ie id r2 tw o8
\n ra qa qd ph jh d2 dx d4 2z jl q5 ld cm wu wi 2t wa dd lg ui to id in uq ww rr g2 wu rl qe 1l qi qo ec yn ed uw p8 ut sj ig p4 zh xm p8 vs vg x7 ot cu l6 sx gu yp t0 gs az pe nf wl qz nj lr cy wr qv tj s7 u2 ly be br ym w2 af ri it ob
\n pp uv nb bu kz wi ah z3 c6 rg la vi oe ia ot pm dv gk 2h xq kq xg bv qr b9 j2 ec od ay p8 qi wp zd ay kg ea mq 6b qc un fh tl u5 av ub ji zx k2 wc zy 1x kc ah rw vc wv yq zv e2 rp
\n a2 iy sa ft pp un qn qz ol lf mg wo fr vu ya rk w0 pj pl el dz i3 jd su ob c8 pb id b9 ep yn ru tk s3 sh sj xa l3 wa nj ke kr ic xl bd ej rg yo f3 al f5 sw re uh h2 av cq bo vk kf bd mu wq wm ew ue tv ol tb o3 ul ov
\n iy a5 gu q2 se ls dt zf o4 dm ez jj uu ik ue w0 ya ea on ui tu rv y1 et r9 tq y5 ht dc fg i2 vs q0 av iv ku in il en ri p7 uc e2 ut sp fv qt gn f2 wo qa op v7 ws l6 wh ys zq t2 wc y3 sx yi t9 t0 ys of rq ug o2 av kn h5 ju ji ko v0 nz wn kf te dw u8 yt fn r1 ie yc it
\n qj lw ji eq oe g7 jf jc yr qo v7 p7 wd ma xg wz qb u7 w3
\n un ol eh g5 px b8 rr og gn mx yf wv sl on jo uz
\n qg qm q5 wy eg ri bm mz d5 rx pt ek fs pi td ez ho gh q0 ll pl kq wr d0 l1 qq ko er qt wf ei p2 ru uq ye tx s4 hc zd vn ps ix zo wk t4 y3 xh ez rf u3 up ys ou xx zv qa wb at rm eu qj wv za zs eq zy rv ry tn tq yc ob
\n ss qa pp rd jf a7 lp h8 um kc q7 wl rg r3 w0 wy tl a0 ih ly qm qq m1 xg qr up ja b8 yh dc lx rw ep ea ev ay ux to p7 tp tb i3 qu gq do gr za l8 rj og oy ub e5 ae tg t0 sq tx hj ad gs it vc bh yb eb w2 yg u9 w5 fn iw si di ah hc
\n ub o0 ik ps qd q1 ga lp cf kl m3 z7 q8 ue ee ud ix g5 ib gd aq fh th pa qc pg ue ur xw ww qr vj m5 jd ng in tx ff xc er qs eo qi p2 ky pd eg e1 yr ut ib oj tb hi hk ho gm qu qi hc ou gn wg sn wh ix wj wj wg ot ra wl un e5 rg s7 t0 oc sm tz hf fx pw x0 wz th qv 1z zt n4 qb cl wn xq xr y8 rt y9 rz tb iw vb
\n qn lq bu eg iw wi 2u 3q t6 k0 yd sd ed rb eh ek yz if pu y6 in fr qc qm ob il ma b4 en wu dk nh b8 qe bb mj ws qt ho yl ug qi ea tw uh p5 eg tl yr i9 pl lh ce i7 wp qa xs dn 7p kr p7 eo vs mb pk ni yh ef rp wj ej y2 iq y6 u5 em e0 ii md jy lw nj fh bq pc xm km q2 wv k1 rx u7 ut et gj tb iw gx fw yx
\n qa pw k6 qn qg qh as u0 dy q7 wp hv 4z 4c w0 d7 et aw wr bl mx md j6 an wi qt lc yx ec tj ri fx ht in gm ua qo qa ik ys eq n7 wh rs wz wx e7 eb ak gg ip sj py ka rl su ag
\n gb uz q2 qz wr q4 z7 ia ad je am my vi zc mx ym r7 yk ua pt g6 hy y7 sx ih qx pa hp jd sy gk nh no qq yg rc 3s qy ep p2 yb oa tr eb p5 en ic yr dw tc in hk qt zp i8 lz ks ci lg x3 wd xa x5 zf yt y5 op u1 oa iw fh oc rk ay pb pw uh qg zj qh h5 nf cd nv qx kp qs qb 6q cl kh xe u7 ew tv sr as rt o4 ey tn is
\n hv qj bo ru z9 t3 lj q9 rg vi rj sd r8 g2 sj yl aq fe po pa qv jf dm qq re we la wt wu qq vt gj ei yz rr xk uk pf so pk im zu ua sg j8 sk zd sd xz zw kl wk ol um yi rh sv u5 pb tz dl oi wz h7 s8 qf wn cx f4 mo wb ed oh ee er ry eu ei oc fw
\n hn a7 cv q6 cj q8 fs jv rl qq qi od dt l5 co qr zq ex u2 ah on pr wx kp wb yh gx
\n gv qg je zg jc q8 fr r6 yn ii g1 pe sj ta el jo sr jv ni jj zr bj ns qr qi ur hz vu wh cs ep s3 hu ez rh u2 t0 dw uj oi wx n5 18 bf wb yq oh ov
\n gb dc um jr mn we bl t6 vi pj c4 d7 rb ia yz tf qn dm ke xb ft au ix tv xd qq xg rx x6 vg r8 wz op h3 qj qx lr xv qc kc wp lq ea rn rm ri eo yx
\n ra gt qs dv ik gn co qg qm qj cb qz z6 wt ji q6 dy qc b4 ws ds vu cf on yg d8 eh py hu tg qc hp qn d3 wq c9 pv pr or qq ml eo ug tw es il os fi uw to em ic oj ho px wo qp m0 qa 1o ks 7o cp wh wk wg x5 ee yn bi ef wj ns r0 ez um u1 iw eb ir fk ov s0 fl hn h1 pr x0 ux cd wz aq jp im k4 qv bp wm n0 vm u7 w4 gj tm uz te a1
\n gv il ps db he nb wr ql kc zh tf mp lw ab us pn a0 tg pa th ps hf wc 1a yw l1 fs eq wp qr rw yv tr eh so i0 qf wf l7 wg na ou ah ay f4 io ip f8 cd h7 nj rn wb qb qn wp oj w3 w6 di id pu eo
\n hm fu pd qk bi wf q6 wu b2 q7 q8 oc lj c3 o7 6s a1 jh rg rj 2s z7 ya id ez fr gh vl cl zq hd jh xh ru c0 bz wu dl qw km kp b9 rn eu yc yc p4 ru tk ux fo ue p9 iv tv s5 do l4 cu rg w6 os fi 4b uz l7 ld l8 fx jb ee wx rp ek tg e8 uf de qh hz h4 qj gq nb wx qc sv go wm zi zo tc 3k ez ec rz ye oh ck w2 sy ia gk rm ei si dp
\n gi go z5 qz wj mg kl yh g5 y6 g9 xt p6 eh ap sa qu dw j8 ql yg aw t7 ir zj v5 v7 ba tw yq cz gc
\n ps qn z3 sw gs q4 ie gx ye wz r3 us ef d9 pb y6 tg y8 qb gc ww az c8 cb lv wy a9 qq qw l2 c8 qu uf yx qo ic de ut e4 uu tb fn oe dp wa uj bq sg mx lv v3 ya xk wd by n7 ra cp gu va yo u2 sv rk ir ya hf kc kp bo qb gp qb yc ku q3 dj o3 ey ad si o7 tw ge uc
\n dx yv ij pw a8 qm ph k9 dz q1 q3 cn wo wp my el bb uo on eh id yz am fe hy sw ha m8 vg wt vl wm qq w3 ls gj yx eo ef en ta e3 i3 zu hl m0 wd co zy l9 nn ea yj e5 rg gi fg gp u4 ir tl tz pm dd kc p9 zx sx qc qv kf ln on qm lw vn vm ew yg se as is di ro gc
\n al tt gu qf qj xo q8 c4 ws e5 ur vp ea rz g3 fw sx th db kq wt sv tb ad hv 1u gt ss xk wj qj pk rp e7 ha fk f6 dr rr hk dk nf qo lr ka ie fk cz yz q3 ym ks gl
\n gb q1 qk we q3 q4 t1 ox di ny wa ws gi ea rx yg r6 io ow y1 d8 ey ab g3 is ek pu ez dx qx th i3 jv fk io xh wt oe kr nd md pb vz wi ro se b9 tr yb p4 i5 p7 ux fp p8 sp in ok hj qy hz wa qq zd qd 3t wj aa dy 5u el yi uo go t0 u5 tl dq gd rw uf gg kb ux dj qj go wb zm lu tx vc es ev ry zt w2 tp w5 tn o6
\n ra ps hm qf qg 4q we ql q4 z6 d1 wp vt xs tg 2s e7 r3 ys oq ef c4 av dj pn aw sr th hf gx uy wr ac zv m6 wn ko c5 qt qy yl yc uh od ri uq fz dq i7 tx fc aa uu qt oe i5 ge ce wa gb vn xg wh og ya xk fs ea yf zw wh ub 8x th iw rj ah ya e9 tl yd tx yf ii fc kx hl zj or qj mh ww kf zm lb ob qn wp ww wn ym u7 rv ie pu
\n uj by wo ml dl qx m3 8i 2y r1 u4 hj h6 qa xv rn rm
\n qa gm qh ql u9 ls e3 yk fa ts wr vj ac en id ud ke ye i7 fn tn f1 ks at me l8 kl hk lx rp ek l6 ek oi e6 wc u4 2h pn 9y zq qk ec cf yq oj vz tb o4 iw ox gx te
\n gt ub hn qd qf hq dh q1 lp qk by ql lq we wr sy wy lh z0 ge k9 w8 th vp pz yg ti fo tp r0 g4 yz ig sx im i1 jv qn q0 wq nu 5t pw wm id qe gg qt xg wh en uc tz pj e4 tv i0 ff qt gm dp jb qp cr gr qa nk ws os wf ne wd mm wf t1 vu wz t8 e7 t0 od hs dq df av km v5 kl we fg cx al ax yq y9 rx yh ul hc
\n we cf q4 cj bf ws ww yd tk ef ek y8 qv fk wt ko qe ep rt ik ut op mr j0 ej t3 s8 ir pt qk km ww cg wc gi lu n6 yr rc oc
\n ak ft gy rd hn a6 uq q2 q4 lr ia eg d1 eb on sj dj pn pp qv i4 hs gx ww xj m8 ko im rt fi tc uy tb pl qy pc uf kf kt mv l0 qj x7 oi um tf ap uk wl ql zb vj wv tk re y7 de q4 rc ad rm ul is fq yx r2
\n dx gb he dl k9 z3 qk lf ad ch js o5 vq zl rj wr th r4 tu uo r8 fp ic g4 fs g6 im fr wq bg no wt dg ru ln rp wi yd qq xz ew i1 kq qt rq wg sb pj hk qu vx oi jm pa vi x5 wf ni ro ot oy di un y6 yo rh sb us tz ac f8 av ve h5 ji mj n2 ci rm yx ep rt 5f yq u9 rb hx aj
\n ih a3 pi mt do w7 zc nh qe wv rs xc qr ts ut pj im hp xa lv x3 ph tt sc od rr qh km oc rq xl vv jp ef st tw
\n ft ik a7 lp jz jx k4 hz wo bv q9 6t ur rl qx qv js dm fw wd re ea fd hu jc qu zo p3 lx pf wk vf fj 2o wx sb gs it ol yp di eo ro
\n tr ih yv h9 k5 qj qk nb wy gk q8 c6 mj a5 yi ur rl uy eq up yj r8 xs sh a0 ez oi y8 ly lp lx fu il ke zy cj sk xq 7u ey p2 uh yv qp rt y9 eg pf so ph tx tc uy e4 tn j0 gy ik vm ul mt nm mm x6 wj rp eh sb u4 ov of tc jt pt qj jy k1 s5 qs 7z do zi cx wq wb ma wm uw sr w5 o3 o5 su r1 yx fe
\n h9 gm cp z2 fb zd gk ve o7 mt bc wp bd rg w8 rk kx mv uu rb or yk eh r0 y5 ht pu tf se ar fj hp su m2 cb c9 c0 b3 ns qq qw rv gg ij y9 oa od of pg i7 hk do pm 2n sj wa vm zw vg yy om qx nj v9 f3 ee w5 w6 iw sp ep
\n da jq iz z1 ls z5 cg nt zk 1i gt w7 yi r3 xi yn pc fa ta ez in i2 qc uw si qw d5 kw il b4 fa ib 6c ud rq yl wg tr qp p4 ry sm ut s5 hi qi do j7 jn j0 qs iz p7 wg aq ex go ax ku nc h8 n3 v0 oc ah wm li zp rl h5 is eu o6
\n gr sa a4 ik dm gp q1 wy m5 fw a2 t6 rj mx e9 et ej q0 ot wu em fa mj qe yg gh j2 te tj p5 eg tk em ao i7 di lh ce m9 wa wf ys eq l1 t3 ej el tf t9 rk u4 ay rw gg dr hj ac qg zk h6 dz ok zy kc mr fz iu hk yj oz ey ag id r2 ov
\n ds fu ps vo qf qn pf a8 ph q4 cb le cn h0 jg gy b9 rk r3 pj yd oe ht pu ig ez hu q0 qm nu ww qw ow xh b4 is a0 qq hu bb cn xh oo qo y0 fz ue e2 yr fa pj in sa hj ui sp nj zg wj ge wk xg ra ex vs oz eb pv tl iu x0 ln cq xx iq s7 wv qs zn tl wn lr yr r2
\n az qf fi qn u5 we jq zh wh c4 sd is y5 po oq ki sz qe rm qy yv p4 ye tb ho 1u gq r3 pl td ov u4 hg ax zj wz wb vl vv se 5k eo
\n tt gi pd jk lq q6 wi gx mj ut ax av g7 qv zm lo 5y dh cw xo ve vy xg yv iv i0 qq xg jv l2 yi sc ga pv pn iu ug gy k3 cl oj tv yl di
\n qk se pd gt rz uu d6 io d7 tq gf em ym tu ib oe v3 si wa nm wf qf wg rk kz yd hl wx cy bp mx et
\n uv yb dd k9 ph q4 me o6 nr xa mu ld r5 rb g2 or fe pa fj hp db lu qn nr j2 bk kt xl rn wf qp tl uc dq i7 tx fv ar sf xm mx x1 zw yh lz mr t6 l9 s8 ah u6 x7 yw ol tq eo gc pi
\n ia ys jh wy sb i0 cp u3 ql kl kh
\n po al qg qm d2 gs q2 ap qz q4 q7 kv ah o8 rs 2o ex zx qw z5 r4 r8 y1 is ts y8 qc dm ll we wr sg lb jx wu jv qe ee qt qy es ed ym hw to tx hr s2 oh dt dy af di do qo oo qa w5 uz wh kh x1 t1 hk no r7 rp na sl ej op ev tj eb sb ya pb u5 tx ds o1 hz v6 lw jp k4 wv do wn aj bc wn yw to w4 as ey yk is ig rp o8
\n rs ty q1 wt wy xp e3 wa yd d7 ht ts sz fk su kw xg bw cw qw oo fu od ix sd zu jn qd ci fi xh mo cp ev th ua e0 em kc lm cu u3 n8 xr yq ti yj fm yx tw
\n dx z2 ga kb yo sf sk fd gf i2 vs qw vj vw qe eu mz tu sp xs qs es t0 eb ak uh hl n1 v9 wv kd o8 rp
\n ds qn qj qz cb kz bo wi o6 z9 fq wq ml cv cb lf eq r6 r8 ic y4 am sz sx po jp g8 ze we wu en ew qw 3q lz kw tt ty ti e1 fx ut tc uu s6 ow gm sh qp vn l4 uj op xn wh qk wz rc wh uv um ar e7 uf az uh py h5 lq vt nz lu li lm dd rl er rt yu o4 eu yz if
\n iz ld me z1 y2 dj ar qb b4 l1 mz ij ry to ad xs sd wf eo hj wl ex ie u5 pr zj gt oi wc kg my ex zt ks yg eu aj
\n gr iy ft pq um qj dz wt gj cn ru kx q8 ws ue rk eb ee fo jb jf la ji ke qq qi qw rq yb qp il eb y0 iv ff zp l3 xm fi x4 r5 xd r0 ol wc t8 ae iw ox fk of pb qj ku xc ct wc ie xn zi wm rz w2 tb u0 su pi
\n a3 ss je un zf vq zg wo mx d7 pm gd vn eu yk tq ik fu ai qt qf j0 to yy at ii qk wz lw n7 ly
\n ub dz rv qt rm wg ea pc j9 qa mr h8
\n a4 wp td ur px qq ki yx go wc tm an
\n po gn rf a5 uw qn q1 nn we is z8 wj ca t5 ij eb tz ef pr ix g3 ek ta a0 y6 sq pa wq cx kq qw we rt c0 mz pv py wi cw mj qt qu 0e oa tj ux pf to hr ao tx yr ts fd fv s5 ui qu j7 gw ug ss cy ks qf xm fk wg vp kt mv qj mn lk vh yt ol rf th os e8 tj ua rk on pq dg kv km wq kp ad bp os bw pb qh wp zs q6 u0 gj yu o5 if a1
\n a6 he pu vd cd q4 jz z6 qc jc bz eh wi b1 ed ym eg fo us ib td y8 gj zm pk lc qr wu mh qw sw fi ue j0 xm kz y2 ev aj df h3 qk qc tx rr rl rc ut ad so ro tw
\n a2 pa we kk eg q7 lh zj wp 4z gi yd yg rc io ix r9 jb xe rr iz jd ij tz p9 qi l4 pa g9 s4 vi tj pb hd f4 qh qk lq qs hn ro
\n gn k9 qz aw wu ki yf e2 pk v8 xk wg 5y t3 sl u4 ya gd hn ql zr oj ig
\n fr qn qh ph k0 q2 nq wi zz rh rx ee ef uo d7 ix el fh qv dm vg px wi m8 qq gh ud qy ec fu yw uw sa tb lg us sk wf 5h qf sh vp wk qk zw qz wg aq of ys ak al f5 re f8 pr ku qx wc u1 lr qs wb f4 cw k2 ka hk mf yr w3 ro ir
\n gr qh ql wh kv e4 r4 oy lu qw pb et uj tb tn xs kr dt td t8 e8 uc xn eq yi af
\n q5 dt ed y1 am qv ut gx m7 yt rr yq yr dy sh mt wd wm th bv ym
\n tr qs ca lp uv q3 wu o5 c1 rx om ee er ta ou i1 jb rt ry os ti fc ss px jn gy jz vp ea tg ay rq u6 al de qc zt wn ez rz eq aa rm ox
\n uv ds h9 fy rf jq he qh h8 d4 wr wf du ck wi km yp ut rv io g1 rb av y4 tw a0 hy sz qx gh ha q9 qw ze 1a bz bx bv qw po ee wa qi xk ri i6 ic he tm hl sj jb qp wp jk qr kf sm l8 be x3 qu ql t1 x6 yy fg rj ua ug qg kv k1 wl v7 xx bj ae wc n3 zy tl tz zk wq re n0 yw oh yr oz eu fm do ux uc
\n hb pa h0 pg q3 mw q6 mg ls lh am sc gz al j2 wt t9 lm qe j2 rm re rr ry fl yw ux i0 2e eo bt vh ra ys sm pb on tx re ff wn wv tu rt ox ul ge
\n al fw zb p6 hi qy ay ou rg sx ag rz uy
\n bx wi kv t5 3w e7 sh ht ff nu la xo qi s3 uy jb 1o vm vi l6 be x3 ny pk aw u1 rk fx km qc be rw yn ey eo ro
\n dn q3 jf w0 e0 lh rv zv js j2 xg ld hr qe mk s3 dr kw kc dh h2 ql cg zv n3 ym yt aa as
\n ft ty qh pi d3 qz ip wu wi q8 wj a1 mg mj ut wt r3 om ua y3 ou fd dc zw lp xe cn dh ng qe kp qt mz xy ef ay od tz p8 i0 hu hz qo kw jw qf kf 3y v3 qj w0 ib ew t4 fg oc e9 ua ov hs u6 f9 h6 vj qv li wq iu yv xv rc w6 rm r2
\n d2 d4 av jc si rs ut 5q pa mm s4 e5 tc km
\n qa uk uw qh vd d3 q4 xo wo c3 wl wa w7 w9 mc r4 y2 fp r0 tw fr g8 ae qn lp sd bk en vr gd hu j1 xv xg rw ep wh ed fu ul eb fl fz i7 ht jx ns v3 ll zs j0 op xf qf l6 l7 sn wk zw wg ej ti wb wz t6 oz rh rj uf je av dj h6 ql wl oi im v8 zr qv wn ku wb bj ef o4 yl r1 ei so if uc
\n k7 qg q5 kx oz mu wl ws rh b2 rk yh qn qe 6o 1y mj ei pf ye e1 dw hj hx do qo gc rh v2 zw x5 t1 t3 yu th e9 em au qh f0 qk km ql kp wc 5p vx bg ea ev wn wm w2 rx o3 yk ru
\n a2 gr qa az dd gm d1 k9 hr we bz lg mg ny wp xd mp yo pj uy xs ua pt g4 tw ez jv q9 qn wr nd md nf qq ng pi bb j2 eu yl ij ty sb os eh hw i6 hg m9 nj wa qs w5 qf nz zh hw wh be zo fs rz me yk y3 ub t7 t8 u2 u3 en ha fl yd hf qh qk wc 1x ze w1 oj ee w3 ey du uz id ah pi
\n o0 hn qz q5 du 2b bn a4 ex rj y1 dj yz y5 ig tf th js qv 5e gc j3 ls d8 yw bc cz tx mb wf ij ty ai as hi lh l3 qd n1 7o wf wh qh wg ot ra l6 el e5 s8 dg vm 3f 7o wn yw gj tb et
\n fu a5 cp ch hx hc rd eh tg g4 li sw sf il eg eb zj 2o cg ew uc
\n qs uc rp ml eb yd if pa c7 oq vk wu ot yq rl uz tv gb zu vm w0 sc tf ud qk wm ko yy er tw
\n ak tt ub pa pq il jq q1 hr k0 uv ql q3 kk zs q5 z8 is lh q8 w7 qw es ii av yz dl ht td g6 vs jm zr px bj no g2 g7 la c7 xt mz yx tt ym os to s1 ur ta s3 gv pl qy pc qi sh jv j8 gr l3 bi oa wd fy v1 s1 zp hg 5e t1 rp wj ms wl t5 el wm at fl e0 sq h3 dl qz ka ox tj qb wb wq re xr rx em ee yu ri do uc a1 rp
\n uv rs un qs um il ul q2 jy kl wo a1 rj tj pk ys r5 yn uo oe y4 ou y5 ar zb g0 qn gx zt lb 2v rm qy nw yz lm eg og i7 ht ss qy qi ge wf bw lv lb wh cs wk hf 7g yb zw hk ns ol rh th yo f4 rq dt uj qo fd wx nk rv ka fl mu rr q3 w2 oj ee rt w6 ru ul
\n qs hq qf qn d1 k0 q2 kk o5 na si bv mx e9 tz d8 tq dl ez qv jf zw ww wu xo b8 w3 i2 te p3 ry iz s1 ut as s5 hk wo wa l6 wh bq xk wj wd ra gu yi u1 t0 ak ai tc ax ip uj nf zb wv bd wo fz qm qj pe md ew rv gj ol yk tn iq yl is si ie r2
\n d3 ni wr ws li mj ds sh sl qx vs rp ft ik e1 sd af ho xn wg zh 6b rp eb f3 u5 uf df py k1 wz vk vx k2 dg wm er rv rb
\n gv iz qz wo o7 k0 oq ti r9 us ib ps g0 jm jb tq ue iv pj sa cr kh t1 ot wc at e9 ys o2 ab qj ww za rn yi
\n ty qn qh nt ql la q3 kl wt q5 mp mg o9 ls lh g2 id ez sw hu qb cx nu pl kw wt vz v2 1t wi mh qr eu rm qs xb ei ij uj yb sn ai iv pj oj de hy gv ka lz pa nx wg wj kj cw zp wk eq wj t5 ns rf e6 rh ev t9 eb ir e0 sm gs gd ds f5 dd de fc f8 x0 lm qz wz xc wc kx cu wv ks lv kv wn pv ei wm ju ww yv zy yt e2 rb w6 oc o6 tq ig
\n q2 o2 gj q6 zh mg li mo vo ch tl ax ip ho wt ln ro wo qr tr tt os fo e1 de hg gb sk w5 fp kg mm l6 yi u3 fl hs u6 fx re rr dl wb jr el rw pm rt to rx w4 gx
\n yv a3 qa uz k9 q2 o2 bp ny zl mj vo tk rx ui g5 pu qx nr xt ls lm rv qs yv ik fz tv wp nk gn qh qi yf ek e5 pb au tc ac kc br qz 4t qc mx ly wm kb ez w2 u8 ei o8
\n rf vo q1 o2 wt q5 oc 5l a2 es oe sc cx wq qw ky em tx rm p2 qo ft ed uq fs i9 ok lz v3 au xj v4 wf l3 eg ej ex wl rv qv ak mi hm cj yr w4 si
\n a2 tr qa fy qd qh oh kk lq dy bz oc jf wa k0 ip pm po qx wq cx we wr bj j5 yq qe gg rq rr oo ru od ix qt af or sj qo jn gr sk wd nc xo xl wk hh yf eh ek aq ex ar en tl ys rq pm ug ql qz wb wn yw og xw an if it
\n ak kk wu ig df w0 rb y4 fr gg i2 qb qw yf j1 ij ue s2 qt ad i5 yp ai l9 wk km ql zn yb ee rb ir te
\n hb q2 ld wt q7 qm km ws w7 vi iu yf rc g1 pr ot a9 pn dk ib qx hi qc jd jg hd q0 lo jj cn wb mz ec qp uj y9 tu yq au tp hg pl jx vx j7 wd 3y ca au rq kv qg dh k2 ok cf qa wv bp dw iu de k2 rt hj wm rc er o3 ey fn si if so
\n hq q1 qj d3 ws as ld mu rj ut d8 ey ou ib ez gf y7 qx qn vz qm vd zw ww d8 xu v1 av b6 mg gs bb g8 rm qy yv rr ry oa tb dt jb qa j0 qs l5 nz qg wj t4 td t6 eb ua s0 pn ii ac x9 qj k4 wc v9 s7 cj zy wo 10 hn yq vz u0 fm uz ux
\n ra pp qd d2 vg wj qn rh we ht st jm bv wu wi qy y9 de gw wa sb uz r1 qu pz ot td rg go e8 sn iy zr wc s0 ww ea pw ac w1 h4 w6 rp
\n fr uv pa jt qk q4 ls wu wi mt xa vu tk ed rb pe fp am sr hp qv m1 gc en yw qq qt ud ey eo p3 tj tu en ix ux ye tp ic s3 ad hz qi uf qa kr wh vd lk yn 5t u1 t0 od rr x9 f0 zj kn nk wp zs se rv ei pi
\n yb dc qs py qk nb jj ql q5 m3 mg zz e5 i1 zr lc zx xu vq hr xp by ei yx gl qi qp ij eg ai ph ap tn dy zp qp bp kf j5 ib vd eg el yy td yo ie ag em fc kx zx h8 wv sv q1 te wv vm yq ol if a1
\n ak rd rf qg lo nq xy z6 q6 mw c1 cu z8 q7 vw wp a2 zz w8 yo uu ee yn r8 av yz y6 pp uq dv i3 db jv q9 2l xg rp ib lj sq tx tc mj mk qr rw te p3 ik eg pj e3 i9 im tb tn fm na j7 lj wa ct rg v4 he x3 kl bi r7 wj y1 l6 wk el 9f t6 gu tj od e9 tz re uh o2 zk ki cf lw jp sc s6 qs qb yn yw ms md w3 rv as yi ox du yz ir yc dp hc
\n yv gy ik k5 db gm ux qj gc w8 ea g6 dx po jb 5t pv wi rz qp jv v1 ea en al ii dt qj py w4 if ux
\n iy pd yg qq p4 in qa y1 yy ta fb zk s6 lu
\n ql ws rv yj jk ke lm ff lb fx s4 av uv wl n1 rv dp
\n qh d3 rs ih rc aq we 7y ud t3 h2 zt cu oc
\n fr hn k6 je q3 k4 tm zh lj aj li a4 t7 w7 kx ut pl rc ih th hp wq pl ls ma oe lf wu l1 ve lp qt qy fi ti he oh hi ow tm cu p7 nr va r3 tt wk wc s8 s0 hs al o2 hk x0 qj lm v5 wl qz 7u iw os lu ah wm hk e1 o4 rm fq ro so
\n gr sa rd um pf ca ga ql qz wt ld z6 vw kv my xt cn wr eq tk rw fe qx qc qr qr rk qa qi ex p3 p5 dq ff pc sh cy oq v4 wg s7 yo ya od rq dt un bw zm da bg q5 ru ah
\n iy qd k6 dm oj qz zd vr w0 r7 d8 et y1 eg yj gz qq p3 il i8 ge wp sx s1 wj t4 lm jo qp pw xc vm sr uz ig
\n qf u8 iq rg rk g4 im ih oq fp a0 ib tc uj tn nc kz ll u1 un qv ck lv vv
\n a4 df um jg z2 lq wr tn xu id wo ez rk rz ew d5 ti ix to r0 sk fa fe fr hp jk wr v1 ms wu jm rc qt tr ex uk vr to tz ut hl sg hb qd xn rj jc 6x mo wz rp ek y4 oi oo ae sc e9 od pn hg wz js qv ln ju rx yg as rn gk o7 so it
\n dd qs qd py k9 lw wt db gk zf nw wh t2 nf zx w8 rj ue w0 tl ew r6 ui ef g1 or ej pn an tq ou ff qv gj jv q0 kq 2l uo oe ku wi w1 tx qe rv 1l ws eu lv p4 ru fo tz ph ib fv uu i3 qu oe pc gw wp sx zd kw w6 bo w9 cs cw my qz zf rg fh e8 ay yd fz rw fc ng qz cg wx qp oz qv ly ha cx al iu rt mp e1 ee w4 tn ul ru o6 ag aj
\n qa kv e5 bm yj j4 m5 rj qe ri ht oi qf qe t6 e5 aw t8 wc dw un yb
\n iy jg jj d4 ju ol wy bs wk wq t8 a9 y4 ta dx jp qc q9 su si ut q0 m8 qq 4a zy qq zq sw po qe qt la sn p6 ht oj hy hh qi gc bn w7 au ya kz na oa ox ov je fb or wl qx ze cg we fk tz tv cg uq w6
\n dx sa qa qs db go lo z3 lf ox jc wj 93 tj tk yf ii ef fo ua sk g5 fs el oi fr sx fg se q9 xq qw j3 m6 4h qq l2 eu rq qu qi hs uh p6 ix fx qa i0 wf ke bq ne wh xl ms un ex sx yi ua pb s0 rq ak ao fc pr qj cw wq vy wv u2 3h wq re yw eb gl eu
\n hv gv il jd go qh d3 we q4 q5 ej lk my gv a2 ds ex e7 rk yf fq pu ff qx ho js gx j1 qe kw gm ja ns wy ln d0 cz xt te xu tt sb em ix ic p0 i8 im ui di gt ws os r1 qy pz l6 e5 e6 op sb pv sn ii rr v6 ql le zy pv mt da yq ol w6 yz ag it
\n ds gy dg jf qg uw qj uv q5 q8 q8 q0 qm e5 rk yd tz pv if qn ju cv xy ki qw mj ls yv ru of tz so yr oq qi m9 sc kw qf zh jx wh kg l0 t1 pz un fj os ha sn f3 e0 om ab cw ct nj zy wn fl ww vn fn tn ie ov
\n co qh jr jj cb bi wt q6 ra qm zx ur r5 an fw tg jm re j5 vl em sl xz qe wa 45 nq ex yb ed ef ph e4 tb s6 qy lz cu gm pd gq mc ca 85 yf wf hu sb eb fk fv dt cq lq qz ww wv xr n0 eq ok er et iw r2 o7 fe it
\n ra dx og wd wt o9 i4 it pk qo ic dt hj jl oq sf lz wd ca hi fg f5 ap x9 gq nd iy q6 ep
\n o9 gb a5 z5 q6 wu w0 tl r9 dj if ts ig it zq ll qw qy ep ed ry p5 ut hh dr i0 hl qp qa zs wd ya ot xk s7 e9 om io dl ki k2 wv q1 5g er rb rm
\n qs w0 om ed tk ta th gf ii av og 6o ee o8
\n po ty rf qf he qc hz bv c5 mi rh ew tu ef sh ix r0 d0 pb tq fw ig g8 fh i2 uw hf qw qr sf cn wi fh qt hp nq yv fy tj od ux ut fb hu tn qt oi qs oa xm dm fa nm qx yy oo ec ev ox sb ya rk ys ud jy dz zv qc zb qb 17 tb lt yt u9 w4 rb st et ry yl o5 di ux
\n gb gt az uk gm qh d3 bt qz wd ld o3 bl cm q7 ck k7 we b2 yd ua ew tl rq yg us tq js fk jb gz jn jg qq in qr rq qy 3h c0 qp p3 yn ef sb ym jl sf sh hv qd p4 fj od ix kz ni wj wz tg t0 tj e0 sm om kx ku cw nb zc aw cy qv bq kg wq pn zz wv cd wn yr se o6 pu eo gc
\n gy rf qf k6 qh qj cd q3 kk cj fw b4 fr mj w7 bm wr ya z7 wt w0 r8 ip ti is pn am y5 qb hs jf qw uu wr np qt wi 1t bx qq qw aw er cv qy rw eo oa iz fp iv qi jm nk kr qf xm eo nr w0 qj bi t3 uv wk ek wn ex e5 rf rh ga it f5 pm hm f8 qj gy jp le wc qc lt s9 zu lb q1 ju w1 uw w3 oj tn tq ir r2 te
\n ak gn pw a5 qh k9 qk nb 2l qz wr kv mt gt w7 zx ii oe ug ix sz qx qc ar sr zb su vg qe np yg qt yj sv uk pd uq pf of eh jb sk gy ws ke kd be og kh x6 me wz e6 yo iw ah rw pm rr qg pt lm ql qz wz qs ly wb wn q3 ry rx gj ia o5 ge
\n gy qm d3 q3 ia c1 ta ex e5 e8 eg sy cl jf qe nj nh m9 qa w6 ek iw kv qg ab n4 w5 iq do
\n uv gv qa un az jd qm eg iw nr q8 zj ny c5 vu rl rx yn et ia ua is ot pt hu im gj qv vv xe xu 1g wo vt qt st qy rw qi wh ft es uk hw to p8 fn f1 hp qu sk p2 l4 zf qf g0 fi l7 be ky iv mn xp nn dt 6b ro kw uv ra tp e6 sv s8 sn tl fz iy qh hz ve v8 h8 th wc wy qb xm s9 hs wq zs yq tu en zt w4 dp yc
\n sa ak gi hw qm gp pp qz fm id lh 6w 9h xr w7 ui ow rb oe ia us fq g5 y5 ig oi y6 im gk ze qe gn 3o ye xz jh qe db qi p1 ep re op te y9 os pj e3 p0 zp qa ih l4 cu zg wg sn rh lf fz ic kh ni wh vh wx th ag u3 f6 uj jy k3 2a wc kv lu wo hb eq w1 rb xw yo ei o7 gx
\n hw wd r0 g4 sz b7 pi sn tc in qt zs rg eu
\n iy hv hb hq qf z3 q2 xy ia tm zf jw wq a3 rk w0 d6 eg et is id po tg gh ob jj wr np no wt ja wy xl l2 yr bt bb tc cx qr rn qt lc rw sy ex y0 ru od to tz og ye hr pj de tv e4 qt ad oo qa jq fj l7 fo wh nt pl ro wl vp yy tf va e6 fg th ar s7 os at s8 re df pe hj f0 qx qc x7 lu nc zo tx bf ww pq cg w1 rx id pu it fe
\n qf fi ld 4o ge da t5 zz mo zx vi ui w9 pj rl rz r4 uy r5 sf av ot fq tw sc zv g9 qv i4 ut iy m1 wy xo b0 ud cb qy rq ug wg sn fo ix uc aa i9 ss sd di sh j8 qp xa ep w0 7h wl t7 iw tj ya hs e0 fz hd u6 hh dr dh uk qj qk wx ol xb qv wb s8 15 wy zm jr nx it eo fx ww yv zy to ee yu yi iq ey iw rm uz yz ob
\n po ra ik qd qf je jr lp wr ji ne wu 2b nt wa e7 rx xo ia yl ta ig ff hp gk jf q9 vd si gc qe rk wu by yg rq sb os fu eh em ux ic ao pj hy im du qy cr 2e l5 qw v1 lf mv wk rx dy rp ra rg gi eb hs au ap bo oa sn zi f6 h1 zt ey yz do
\n lj vu y2 if qr wr ta so kg 3k ol u1 f5
\n yb cv mq lf zz ue ui dx qe tv qu ex tz hh tb dy ds wi rb
\n hb gt qa h9 rf qf qg k9 q1 lo q3 ql z8 gl zg q8 1t wo vr rg ez ws mo w9 yo r3 yd e9 ea sf a0 im tg y8 ar qb ni wr qr wv m4 ix d9 nz yq fa if yr bt qr mz qi ea rr xx at ic pk qi za hv kq w7 co xj wd x4 zs wh y3 rf ec u2 e9 ah s0 uh io un h8 iq zb bs nr be zo fz vb wm pr yw md rc ur er ia yl ox ei ux eo tw o8
\n qs rd rh yf px ow d8 tq ig ih cl qr yw qq in qy wc ek rh ya qg cd x9 qm lq to o3 ul a1
\n a2 cp as gk kc 4u zj z2 t5 zz rb eh sh sx fg fh g9 hp vd gx oq cv pe wv b8 qe cc nw tr il tl e3 qu l4 mt wk wh aq td e9 gf lm qz bu jq my wp vb dg y0 ye yq w6 r1
\n ij az qn ph qv bv bf mz iu is y8 ar fh q9 hd qq ji sf ld up qo p2 at sp in qi nk l5 e0 rw px 5o ew oc te
\n qd jr pa q5 w9 hw hu y8 dn qn zw wr ma ei xl dq i7 i9 vb sx wf wh na wl um e7 s0 h2 nh nk fj yl wn iu u7 as ad ey so uc
\n la up ic g5 ay ic x8 u2 ar eb wb yr aj
\n po da rd un qg uw lq m2 4r wg q8 z9 t3 zc d9 ae st q0 li qw wt kr qu ry en sm qf kf kh ny yt gi rh u5 em tc kv h8 qx lr jq ef
\n a2 dh qh q1 h8 qz z6 kx z8 bv 3q df pk tl d8 tq tf g8 zb qw 5p zm qe cv yb ec uz iv e2 gq wp uh kq ws lc wk x3 t8 rj fc io je dk lr lt wv wt bw be eo q4 ye yy rv ok yo yp ir ig
\n rs ij ty ps ul wr bh kb rs z4 z8 er px uo up y1 rb fo jo gg dv ph q0 jn xw ww d8 rp yd yf tx b0 op yn of jl tm px fm jc zf qd pk wh rp uv tp t9 ir yf ug qg v5 ku qz fd k4 cu mw zn iu bg lq ly rv su
\n ik dm cm or tw pu lp eh qd kk j0 em ng tw
\n ra ds qk cg q7 k6 4p t6 yu lq go yd eq r8 fw am dm xy cm v1 cz eo qi ij yn eg hq tc sj qa i0 oa l6 p6 wj vd wf mr yt ex e6 yo t9 ev s8 en rr bq kg hb lm re bj ms w1 et du
\n o9 gy bl wz t8 hq iu ix av y5 y8 jn j1 np xt t9 vw qq 43 xv 9w yi ft es hy op lg vs hg wd ef wx ou ox sw dr ze xr st fm ah
\n a3 wr fb jc c2 w8 rx fe q9 hd xq qq wi te y9 e1 qt qi qs nl ca bh u2 md tv hx
\n ra jh q3 aa t3 1o eq lh rv fo us pt dj pm pi qn zr bj xj cm ix a0 ra hi eu nw yc p3 ru ri ue e2 jl hi wo g9 xn qh wl wx go yp rr dj nd ch u3 fj bd jy vn w1 ia ox tm uz
\n ma y5 bl qi g7 ri fl ap 7a yo ko rp
\n um dh pg wq r5 sf ia ta an hs ne q9 wt sh rk yi ym of tb oq do hv wj ic oi sc pe sc wq wb wm tq
\n hq qm gg q4 xu k4 k7 uo wt kb et fo ey aw g0 xi am in qy eo qi eb ay ue og nt kl wx s9 df qg f9 v6 rv sv pc tl my bj wb eq h4 o3 ri
\n tt uq qh nb qz wt jx ya on om io ow ha qp e2 fd e4 hp hx p2 vm xg xn ra l8 iu yf jr qh k4 1l oa tk zp yw rz sy ul yx eo ep
\n ij yb qs fi ul qk by ql jl wr bi q5 bl tm q7 xp k7 vy gi tj rl rx yf ym tu er r8 pe ip ej y4 fd jn gx zt vj xt xh rj kt cm ri nh zq pp eu rw to pg e1 ue dw e2 so tb gm qi jb nk gy pa v1 xj fl kh 3k kl ed wx wc ek yy ez wc iq yo u4 it tz ak hn f8 h2 hl uv wz h8 gi nk ch zt wt bw kn yq e1 tv zp ag it
\n qs rf jq go qh a8 jj xt le wu qq yd d6 d0 ff qc su c7 lc wp ty y0 tu ti pf ta aa ug vb rf vi rx no un yu e5 7r up rj ag ha hs fc wz bo qv bp tv ki er
\n yb qa rd lo ok q6 o6 ba r6 ow or yl am aq gd dc ho cx c0 wu g4 ib c5 ep re qo ed yw iv ta hy jc pn xs oq xn bo zg ps kr iz yp wh wj xl xo zq me na ek wl wx wc rg uo ir tk aj da rw hm io uj fb jt qj dk nh zx jp qx wc zr zy zu nc xe ww wv vn h2 q6 en ew ad yl af eo if r2
\n yb o0 dn z1 q1 sw qk po xt wr ls z8 ox wu jd ro q8 lh mh wa rg ea c3 pz tu g2 is a9 pn tw po qc sa jj vh ax xh kt wy jv mg nh kp b9 qi od ht e4 uu ij qf sf nc wk ap wd qz rd yi s7 tk pb it f7 o2 f9 kv qh h4 ln wz gi qd zn xm sn 39 yn rz u7 yr yj is ie ag ir tw
\n po dx uv pq jd dm d2 hr cs gs d3 q4 wr np ab di mh vt w7 w0 on tl ia ta aq dz y7 i1 qc pg q0 wq j2 c7 la cb il wr lv na ru 4g vz nf bc sl qq qp qt ud rq wg ex uw he tz ye p9 ui ou vu at ix w0 vf bi ed yh wh ra y6 wc u1 t8 fj ov iy tz kx h1 hj jy qj h6 ng jo wz cj ie mt rq te n9 mp ma q5 8w rb o7 sp ob
\n po ra dg ca qj q2 is kn rd ws lq tu ym yl y5 tg pp qw we cn a7 1t jd m7 wo yr sz qa hp ei qy ec p4 hw p7 to au iv ht qt qy qo l4 lz xm wd yf wg ez s7 en f3 tx yf rr f8 cw ji qv yz ry ew w2 oj w4 w5 st rn oz ri o6 dp aj
\n gm jg ju q5 np lf q7 xo 1y qn k0 jo pp qx th q9 4f or ro pu bv eu nw uq ao dy jb j9 gr rd nz wj wj r9 co ta rk od dd gg hm df pr km ng oj qc sb qd wb tz cq ex wb vb ty eq tv iw
\n fu uq co qk jl cg ld lg wo vr gc bd rj r3 yd rz iu ew d6 io to sh y7 jp db dn qn qm si xg qr ls jo lr wy rk wn m7 qu bb es op qp ru en ta e3 in hy dy hl vc gc gt jw ke 2t wh rk lj hg oy e6 yo ev em fz rw pq re dg qk ku oi qz k4 qv li rq n8 ec 4d yb wb e1 iw id o6 ir do ux pi ep
\n a2 wu jd ef dc mn 5e qp pl xd ag ay
\n yv o9 al a5 uq qg jw pi z2 jt cd q5 3m zl ez vu rg jl rz yf ix sj fp d0 tq ff ha hs zw om ni m0 xg c8 a7 ki qw cc ei xg j3 tt tu il p6 ix tp tx ib sp hg p0 fc pj su qu jv sj lk qp ws qs gm 1x mx lv wj qu l1 dt wh wv un aq fg rg e6 uo ar ie up it sw tx f6 o2 h3 qc qa ho vj u3 kd zy n6 my ww vc lr em w2 se rt o4 yc a1 te
\n qs:1 dv:2 pa:3 ty:4 iz:5 uw:6 qh:7 jj:8 z3:9 tn:10 jc:11 eg:12 e4:13 qq:14 w7:15 ut:16 r3:17 uu:18 kb:19 up:20 g1:21 fo:22 iv:23 if:24 fd:25 gd:26 sc:27 qm:28 qe:29 xg:30 ia:31 wb:32 he:33 ky:34 hu:35 tv:36 rw:37 qu:38 rr:39 es:40 p3:41 ue:42 s2:43 as:44 i0:45 dt:46 qt:47 hz:48 jm:49 j0:50 gy:51 ci:52 fi:53 hw:54 nv:55 ea:56 kk:57 vf:58 rx:59 68:60 ti:61 rp:62 wl:63 oi:64 vp:65 at:66 om:67 io:68 uk:69 pt:70 qh:71 qj:72 dl:73 cf:74 lr:75 cx:76 wq:77 ku:78 ki:79 w2:80 yh:81 af:82 ul:83 sp:84 yc:85
\n ub:1 yb:2 dc:3 ty:4 gm:5 dm:6 go:7 nv:8 we:9 ql:10 by:11 la:12 o1:13 ju:14 o3:15 jx:16 fm:17 aj:18 wa:19 rg:20 e4:21 vi:22 a6:23 r4:24 xo:25 tz:26 oe:27 ip:28 pv:29 dk:30 tq:31 a0:32 tf:33 fg:34 tg:35 i4:36 pz:37 sd:38 ry:39 ky:40 mg:41 hy:42 g7:43 eu:44 qy:45 yi:46 rw:47 qp:48 eg:49 yw:50 hw:51 sm:52 uc:53 i7:54 dw:55 fx:56 s3:57 sf:58 zo:59 m9:60 xs:61 vn:62 rf:63 ci:64 nz:65 kr:66 qt:67 9y:68 pj:69 lk:70 ee:71 pz:72 ef:73 rk:74 e0:75 fx:76 uf:77 az:78 fc:79 qg:80 jr:81 oy:82 lq:83 cg:84 qp:85 um:86 ad:87 wc:88 zn:89 bw:90 n6:91 my:92 xr:93 mp:94 tu:95 en:96 o3:97 iq:98 ir:99
\n da:1 a3:2 d1:3 jr:4 dz:5 ca:6 ql:7 nu:8 q3:9 cf:10 o6:11 nr:12 mt:13 lk:14 yr:15 rs:16 lp:17 w7:18 a5:19 pj:20 ys:21 ym:22 r8:23 ey:24 to:25 fs:26 dz:27 im:28 ih:29 sw:30 qx:31 qv:32 zn:33 gl:34 j1:35 xe:36 lc:37 zc:38 vw:39 6a:40 mh:41 b9:42 qt:43 rm:44 re:45 oo:46 qp:47 p4:48 tk:49 ix:50 p7:51 og:52 tz:53 yr:54 sp:55 aa:56 hk:57 ih:58 lx:59 qd:60 mx:61 4n:62 kk:63 vf:64 el:65 oo:66 td:67 ae:68 yo:69 fj:70 uf:71 pr:72 hl:73 qj:74 qk:75 wr:76 qc:77 qv:78 kf:79 yz:80 my:81 wq:82 hn:83 zs:84 dr:85 ee:86 u8:87 rv:88 et:89 ru:90 ie:91 ag:92
\n gt:1 ph:2 z0:3 zl:4 mu:5 ui:6 av:7 zm:8 om:9 ui:10 vh:11 qr:12 he:13 qr:14 es:15 fl:16 ws:17 w6:18 nc:19 ra:20 rk:21 kp:22 ol:23 wm:24 yu:25
\n dd:1 df:2 jq:3 jd:4 ux:5 ql:6 el:7 3r:8 ya:9 uu:10 iu:11 ee:12 eh:13 g3:14 sj:15 us:16 ib:17 pp:18 qc:19 jv:20 hd:21 bh:22 zt:23 uo:24 d8:25 b3:26 xu:27 bc:28 rq:29 te:30 uh:31 ex:32 tt:33 eb:34 il:35 qu:36 pc:37 ge:38 sj:39 qp:40 ih:41 xf:42 3r:43 gr:44 yh:45 qx:46 tu:47 wl:48 wn:49 sz:50 up:51 ay:52 it:53 ab:54 jt:55 qz:56 v7:57 wn:58 li:59 za:60 6o:61 w3:62 fn:63 yk:64 eu:65 ie:66 gz:67
\n yv:1 o9:2 qf:3 eg:4 eh:5 mh:6 jh:7 rh:8 r3:9 rv:10 ix:11 y3:12 a0:13 sr:14 qc:15 qq:16 qr:17 wr:18 qe:19 bx:20 ki:21 m8:22 mk:23 qi:24 lm:25 uk:26 eb:27 ai:28 ur:29 e2:30 xd:31 nc:32 ca:33 eo:34 mb:35 ed:36 uv:37 rs:38 up:39 ya:40 of:41 hn:42 lw:43 wz:44 qz:45 et:46 qh:47 wm:48 zr:49 rc:50 o3:51
\n ss:1 qg:2 qj:3 ph:4 qk:5 q2:6 cs:7 z4:8 bi:9 qc:10 cj:11 q8:12 qn:13 w7:14 rj:15 ys:16 ea:17 r4:18 uy:19 om:20 rc:21 ii:22 fp:23 sj:24 ej:25 yz:26 el:27 qx:28 qv:29 zn:30 gk:31 q9:32 hs:33 m2:34 ii:35 d7:36 nk:37 c8:38 j4:39 qq:40 dl:41 gg:42 pp:43 ei:44 qo:45 yc:46 od:47 fo:48 eh:49 fp:50 ta:51 hy:52 ok:53 tv:54 uu:55 us:56 dp:57 qf:58 sb:59 zg:60 ks:61 sg:62 n3:63 wh:64 x2:65 nr:66 cs:67 wk:68 kh:69 wk:70 wf:71 ew:72 7h:73 7k:74 oy:75 t6:76 gi:77 rg:78 yp:79 s9:80 ya:81 e9:82 pb:83 tc:84 dt:85 dh:86 hk:87 h2:88 pt:89 qh:90 h7:91 wz:92 n1:93 qv:94 kd:95 pm:96 cc:97 xr:98 kp:99 yk:100 tm:101
\n gr:1 qa:2 ft:3 tt:4 gn:5 qs:6 pw:7 pu:8 ca:9 ph:10 ls:11 cg:12 cn:13 zf:14 bz:15 q7:16 z0:17 c3:18 qn:19 gv:20 w7:21 rg:22 ut:23 e8:24 ii:25 er:26 ip:27 sg:28 y3:29 oy:30 ek:31 ht:32 gd:33 qx:34 g0:35 qv:36 db:37 su:38 jn:39 qq:40 lz:41 uu:42 jo:43 ru:44 an:45 wi:46 kn:47 sq:48 nh:49 qr:50 qy:51 yx:52 eo:53 qi:54 xz:55 y9:56 ru:57 pd:58 au:59 p7:60 dq:61 he:62 ut:63 ok:64 fd:65 jz:66 ui:67 hc:68 j9:69 l3:70 hb:71 xd:72 jq:73 gy:74 kh:75 wf:76 xs:77 sl:78 rs:79 aq:80 ez:81 y4:82 ts:83 um:84 yi:85 e0:86 gh:87 dk:88 py:89 v5:90 qk:91 ql:92 ko:93 jq:94 wc:95 nk:96 v0:97 wb:98 qv:99 br:100 iu:101 wm:102 6p:103 sr:104 gk:105 yp:106
\n o0:1 pa:2 pf:3 z3:4 jt:5 jy:6 z7:7 cm:8 ne:9 w8:10 yz:11 fd:12 fg:13 zn:14 qq:15 ll:16 vg:17 wr:18 wb:19 ia:20 xx:21 yj:22 ty:23 eh:24 e1:25 so:26 ts:27 tc:28 s4:29 i0:30 tn:31 wo:32 wp:33 wa:34 op:35 va:36 wk:37 x3:38 vg:39 qx:40 rs:41 sn:42 au:43 f3:44 tz:45 sq:46 hn:47 rr:48 o2:49 fv:50 un:51 k2:52 vj:53 ey:54
\n iy:1 ra:2 ij:3 ty:4 a4:5 un:6 rf:7 qg:8 dm:9 jr:10 kj:11 uv:12 we:13 cv:14 gk:15 wy:16 z8:17 oc:18 wp:19 mo:20 jl:21 mz:22 ev:23 ch:24 rv:25 tu:26 ax:27 y2:28 g3:29 oy:30 y6:31 im:32 uq:33 qv:34 hp:35 qb:36 hd:37 iy:38 lp:39 nk:40 w2:41 bb:42 ho:43 ep:44 tr:45 os:46 en:47 sm:48 p8:49 p9:50 hy:51 ss:52 ui:53 gm:54 qi:55 oo:56 vn:57 ae:58 qd:59 w6:60 ps:61 dn:62 wd:63 wg:64 ro:65 mr:66 yt:67 ol:68 oz:69 rg:70 s7:71 u5:72 tl:73 yd:74 rr:75 ax:76 f0:77 cq:78 ku:79 qx:80 ze:81 n4:82 wn:83 kt:84 ca:85 jy:86 bg:87 yc:88 zs:89 yw:90 rz:91 w1:92 eu:93 rm:94
\n hv:1 fu:2 ca:3 q8:4 mt:5 la:6 r3:7 pl:8 yh:9 to:10 sy:11 yh:12 tv:13 x4:14 tg:15 yp:16 ov:17 wn:18 ze:19
\n o9:1 qa:2 az:3 gm:4 qd:5 pw:6 hq:7 pd:8 ga:9 qj:10 cd:11 q3:12 jk:13 pd:14 du:15 c2:16 zk:17 xf:18 t8:19 eq:20 om:21 es:22 rc:23 ua:24 y3:25 pu:26 ig:27 qx:28 se:29 qv:30 db:31 st:32 qn:33 ii:34 lx:35 qe:36 wt:37 xk:38 nx:39 ku:40 br:41 qe:42 qr:43 qt:44 rm:45 eu:46 xf:47 xb:48 rn:49 qu:50 qi:51 ep:52 qo:53 rr:54 ex:55 xk:56 p5:57 ym:58 fi:59 uq:60 to:61 ux:62 ix:63 ai:64 hj:65 gn:66 zi:67 oq:68 qf:69 kd:70 wf:71 xn:72 kr:73 w8:74 rl:75 kk:76 mq:77 rp:78 rf:79 u1:80 s7:81 oa:82 fh:83 e7:84 yp:85 e0:86 pr:87 ql:88 sx:89 ck:90 ag:91 kg:92 kt:93 mp:94 eb:95 rl:96 em:97 ee:98 w6:99 du:100 rm:101 yz:102 if:103
\n qs:1 pi:2 am:3 6a:4 ut:5 r4:6 ii:7 sd:8 ua:9 ib:10 y6:11 pa:12 kt:13 pb:14 wm:15 qq:16 dz:17 qt:18 qp:19 y0:20 he:21 p8:22 ue:23 tb:24 qu:25 or:26 qo:27 wh:28 40:29 8j:30 t4:31 sl:32 rf:33 iu:34 gh:35 hh:36 qc:37 iq:38 bf:39 rl:40 wm:41 mf:42 oh:43 ew:44 is:45
\n da:1 ps:2 a7:3 jr:4 z4:5 q3:6 bu:7 xt:8 ip:9 jx:10 q6:11 np:12 z7:13 bp:14 lg:15 bz:16 ye:17 wo:18 ig:19 bb:20 ww:21 rf:22 om:23 uu:24 ef:25 r8:26 ey:27 pt:28 ta:29 pn:30 y7:31 gg:32 th:33 dn:34 pg:35 qb:36 ri:37 qq:38 42:39 qw:40 zw:41 b9:42 b0:43 xb:44 qt:45 qy:46 yl:47 wh:48 xk:49 ft:50 at:51 yw:52 i7:53 sp:54 de:55 pz:56 fn:57 qy:58 si:59 m0:60 ik:61 wf:62 sg:63 cq:64 ql:65 hk:66 er:67 eg:68 lc:69 ek:70 na:71 wc:72 iw:73 ir:74 rk:75 ua:76 e0:77 ak:78 iu:79 sw:80 ap:81 uj:82 av:83 ab:84 hl:85 uv:86 zc:87 qa:88 wc:89 jw:90 vj:91 qv:92 vk:93 ay:94 kg:95 xw:96 on:97 rw:98 1b:99 wn:100 rl:101 eb:102 vm:103 eq:104 h4:105 yt:106 oz:107 eu:108
\n a2:1 qa:2 rd:3 cp:4 qh:5 ub:6 vg:7 ws:8 u0:9 4g:10 bp:11 da:12 yo:13 ev:14 kz:15 eq:16 uu:17 ee:18 ef:19 yk:20 pr:21 sj:22 sk:23 fe:24 oi:25 lt:26 uy:27 j2:28 gn:29 io:30 vk:31 ns:32 27:33 ln:34 wu:35 ve:36 yr:37 l2:38 qu:39 qi:40 ry:41 il:42 ul:43 tj:44 eg:45 ux:46 i5:47 yr:48 tx:49 ph:50 oj:51 gq:52 or:53 zp:54 wa:55 qs:56 gy:57 iz:58 v0:59 qt:60 wj:61 ic:62 ca:63 lh:64 yb:65 np:66 ej:67 sl:68 td:69 l8:70 yi:71 iw:72 s8:73 e8:74 ys:75 yd:76 sq:77 al:78 dt:79 pt:80 tg:81 te:82 yb:83 eu:84 tq:85 r1:86 ir:87
\n tr:1 h9:2 go:3 qj:4 b1:5 wu:6 q7:7 zh:8 el:9 tg:10 mb:11 ys:12 ed:13 ii:14 er:15 r8:16 xs:17 pe:18 r0:19 sw:20 db:21 ov:22 m7:23 d3:24 re:25 no:26 nu:27 zr:28 pq:29 ji:30 wr:31 lc:32 or:33 qw:34 ee:35 yy:36 qr:37 rn:38 rm:39 re:40 qi:41 te:42 ea:43 qo:44 yb:45 yn:46 y0:47 uz:48 uq:49 iz:50 tl:51 yr:52 i0:53 fm:54 wp:55 qs:56 qd:57 he:58 wk:59 kl:60 ew:61 as:62 hl:63 ez:64 oo:65 ox:66 ie:67 s0:68 f4:69 dl:70 wq:71 wl:72 ww:73 lr:74 qc:75 qv:76 vk:77 mt:78 my:79 kn:80 ep:81 em:82 yt:83 sy:84 am:85 so:86 rp:87
\n ak:1 ft:2 qg:3 bg:4 ji:5 q6:6 bk:7 xi:8 tf:9 mo:10 ur:11 pj:12 yk:13 ua:14 qv:15 wq:16 gc:17 qe:18 ke:19 ef:20 eb:21 tk:22 uq:23 i6:24 oh:25 iv:26 gb:27 qs:28 rx:29 el:30 yo:31 rr:32 pe:33 wz:34 wx:35 ho:36 xq:37 tc:38 mo:39 yk:40 du:41 r1:42 tq:43 oc:44 hc:45
\n ra:1 ub:2 qj:3 jh:4 jt:5 dx:6 ql:7 q6:8 da:9 tf:10 r3:11 ew:12 iu:13 sg:14 tp:15 yl:16 el:17 gc:18 6n:19 tt:20 ry:21 pd:22 ye:23 ff:24 lz:25 kt:26 yp:27 fl:28 yf:29 dl:30
\n o9:1 hb:2 h9:3 qd:4 dh:5 qg:6 q1:7 qj:8 jy:9 se:10 q5:11 wt:12 nr:13 qv:14 ge:15 c5:16 el:17 6y:18 uo:19 rv:20 ax:21 pe:22 et:23 r0:24 fe:25 y6:26 dx:27 qx:28 ha:29 qq:30 lo:31 we:32 zy:33 v1:34 wy:35 dl:36 vr:37 wa:38 qr:39 rm:40 qi:41 qp:42 yn:43 tz:44 pg:45 ph:46 de:47 p0:48 do:49 qp:50 wp:51 wf:52 bw:53 xh:54 ky:55 xz:56 wh:57 hl:58 to:59 ek:60 rd:61 sv:62 rj:63 rq:64 re:65 h1:66 qg:67 qh:68 kl:69 f1:70 zm:71 18:72 ez:73 xe:74 vm:75 en:76 5j:77 o3:78 rn:79 fw:80 fe:81
\n db:1 c2:2 bb:3 o0:4 w8:5 kl:6 kc:7 y4:8 qx:9 zm:10 pk:11 cw:12 id:13 ve:14 mh:15 lp:16 rq:17 jb:18 fl:19 x1:20 qi:21 wd:22 lx:23 f3:24 cy:25 bq:26 dd:27 ye:28 fn:29
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More