diff --git a/go/mysql/sqlerror/sql_error.go b/go/mysql/sqlerror/sql_error.go index 9b1f65c82e3..30ce99d681a 100644 --- a/go/mysql/sqlerror/sql_error.go +++ b/go/mysql/sqlerror/sql_error.go @@ -243,6 +243,7 @@ var stateToMysqlCode = map[vterrors.State]mysqlCode{ vterrors.CharacterSetMismatch: {num: ERCharacterSetMismatch, state: SSUnknownSQLState}, vterrors.WrongParametersToNativeFct: {num: ERWrongParametersToNativeFct, state: SSUnknownSQLState}, vterrors.KillDeniedError: {num: ERKillDenied, state: SSUnknownSQLState}, + vterrors.BadNullError: {num: ERBadNullError, state: SSConstraintViolation}, } func getStateToMySQLState(state vterrors.State) mysqlCode { diff --git a/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go b/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go index f7d7340d6a4..4c84eae2f42 100644 --- a/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go +++ b/go/test/endtoend/vtgate/foreignkey/fk_fuzz_test.go @@ -95,9 +95,9 @@ func (fz *fuzzer) generateQuery() []string { if val < fz.insertShare { switch fz.queryFormat { case OlapSQLQueries, SQLQueries: - return []string{fz.generateInsertDMLQuery()} + return []string{fz.generateInsertDMLQuery(getInsertType())} case PreparedStatmentQueries: - return fz.getPreparedInsertQueries() + return fz.getPreparedInsertQueries(getInsertType()) default: panic("Unknown query type") } @@ -122,22 +122,26 @@ func (fz *fuzzer) generateQuery() []string { } } +func getInsertType() string { + return "insert" +} + // generateInsertDMLQuery generates an INSERT query from the parameters for the fuzzer. -func (fz *fuzzer) generateInsertDMLQuery() string { +func (fz *fuzzer) generateInsertDMLQuery(insertType string) string { tableId := rand.Intn(len(fkTables)) idValue := 1 + rand.Intn(fz.maxValForId) tableName := fkTables[tableId] if tableName == "fk_t20" { colValue := rand.Intn(1 + fz.maxValForCol) col2Value := rand.Intn(1 + fz.maxValForCol) - return fmt.Sprintf("insert into %v (id, col, col2) values (%v, %v, %v)", tableName, idValue, convertIntValueToString(colValue), convertIntValueToString(col2Value)) + return fmt.Sprintf("%s into %v (id, col, col2) values (%v, %v, %v)", insertType, tableName, idValue, convertIntValueToString(colValue), convertIntValueToString(col2Value)) } else if isMultiColFkTable(tableName) { colaValue := rand.Intn(1 + fz.maxValForCol) colbValue := rand.Intn(1 + fz.maxValForCol) - return fmt.Sprintf("insert into %v (id, cola, colb) values (%v, %v, %v)", tableName, idValue, convertIntValueToString(colaValue), convertIntValueToString(colbValue)) + return fmt.Sprintf("%s into %v (id, cola, colb) values (%v, %v, %v)", insertType, tableName, idValue, convertIntValueToString(colaValue), convertIntValueToString(colbValue)) } else { colValue := rand.Intn(1 + fz.maxValForCol) - return fmt.Sprintf("insert into %v (id, col) values (%v, %v)", tableName, idValue, convertIntValueToString(colValue)) + return fmt.Sprintf("%s into %v (id, col) values (%v, %v)", insertType, tableName, idValue, convertIntValueToString(colValue)) } } @@ -332,7 +336,7 @@ func (fz *fuzzer) getPreparedDeleteQueries() []string { } // getPreparedInsertQueries gets the list of queries to run for executing an INSERT using prepared statements. -func (fz *fuzzer) getPreparedInsertQueries() []string { +func (fz *fuzzer) getPreparedInsertQueries(insertType string) []string { tableId := rand.Intn(len(fkTables)) idValue := 1 + rand.Intn(fz.maxValForId) tableName := fkTables[tableId] @@ -340,7 +344,7 @@ func (fz *fuzzer) getPreparedInsertQueries() []string { colValue := rand.Intn(1 + fz.maxValForCol) col2Value := rand.Intn(1 + fz.maxValForCol) return []string{ - "prepare stmt_insert from 'insert into fk_t20 (id, col, col2) values (?, ?, ?)'", + fmt.Sprintf("prepare stmt_insert from '%s into fk_t20 (id, col, col2) values (?, ?, ?)'", insertType), fmt.Sprintf("SET @id = %v", idValue), fmt.Sprintf("SET @col = %v", convertIntValueToString(colValue)), fmt.Sprintf("SET @col2 = %v", convertIntValueToString(col2Value)), @@ -350,7 +354,7 @@ func (fz *fuzzer) getPreparedInsertQueries() []string { colaValue := rand.Intn(1 + fz.maxValForCol) colbValue := rand.Intn(1 + fz.maxValForCol) return []string{ - fmt.Sprintf("prepare stmt_insert from 'insert into %v (id, cola, colb) values (?, ?, ?)'", tableName), + fmt.Sprintf("prepare stmt_insert from '%s into %v (id, cola, colb) values (?, ?, ?)'", insertType, tableName), fmt.Sprintf("SET @id = %v", idValue), fmt.Sprintf("SET @cola = %v", convertIntValueToString(colaValue)), fmt.Sprintf("SET @colb = %v", convertIntValueToString(colbValue)), @@ -359,7 +363,7 @@ func (fz *fuzzer) getPreparedInsertQueries() []string { } else { colValue := rand.Intn(1 + fz.maxValForCol) return []string{ - fmt.Sprintf("prepare stmt_insert from 'insert into %v (id, col) values (?, ?)'", tableName), + fmt.Sprintf("prepare stmt_insert from '%s into %v (id, col) values (?, ?)'", insertType, tableName), fmt.Sprintf("SET @id = %v", idValue), fmt.Sprintf("SET @col = %v", convertIntValueToString(colValue)), "execute stmt_insert using @id, @col", @@ -407,7 +411,7 @@ func (fz *fuzzer) getPreparedUpdateQueries() []string { func (fz *fuzzer) generateParameterizedQuery() (query string, params []any) { val := rand.Intn(fz.insertShare + fz.updateShare + fz.deleteShare) if val < fz.insertShare { - return fz.generateParameterizedInsertQuery() + return fz.generateParameterizedInsertQuery(getInsertType()) } if val < fz.insertShare+fz.updateShare { return fz.generateParameterizedUpdateQuery() @@ -416,21 +420,21 @@ func (fz *fuzzer) generateParameterizedQuery() (query string, params []any) { } // generateParameterizedInsertQuery generates a parameterized INSERT query for the query format PreparedStatementPacket. -func (fz *fuzzer) generateParameterizedInsertQuery() (query string, params []any) { +func (fz *fuzzer) generateParameterizedInsertQuery(insertType string) (query string, params []any) { tableId := rand.Intn(len(fkTables)) idValue := 1 + rand.Intn(fz.maxValForId) tableName := fkTables[tableId] if tableName == "fk_t20" { colValue := rand.Intn(1 + fz.maxValForCol) col2Value := rand.Intn(1 + fz.maxValForCol) - return fmt.Sprintf("insert into %v (id, col, col2) values (?, ?, ?)", tableName), []any{idValue, convertIntValueToString(colValue), convertIntValueToString(col2Value)} + return fmt.Sprintf("%s into %v (id, col, col2) values (?, ?, ?)", insertType, tableName), []any{idValue, convertIntValueToString(colValue), convertIntValueToString(col2Value)} } else if isMultiColFkTable(tableName) { colaValue := rand.Intn(1 + fz.maxValForCol) colbValue := rand.Intn(1 + fz.maxValForCol) - return fmt.Sprintf("insert into %v (id, cola, colb) values (?, ?, ?)", tableName), []any{idValue, convertIntValueToString(colaValue), convertIntValueToString(colbValue)} + return fmt.Sprintf("%s into %v (id, cola, colb) values (?, ?, ?)", insertType, tableName), []any{idValue, convertIntValueToString(colaValue), convertIntValueToString(colbValue)} } else { colValue := rand.Intn(1 + fz.maxValForCol) - return fmt.Sprintf("insert into %v (id, col) values (?, ?)", tableName), []any{idValue, convertIntValueToString(colValue)} + return fmt.Sprintf("%s into %v (id, col) values (?, ?)", insertType, tableName), []any{idValue, convertIntValueToString(colValue)} } } @@ -555,58 +559,61 @@ func TestFkFuzzTest(t *testing.T) { insertShare int deleteShare int updateShare int - }{ - { - name: "Single Thread - Only Inserts", - concurrency: 1, - timeForTesting: 5 * time.Second, - maxValForCol: 5, - maxValForId: 10, - insertShare: 100, - deleteShare: 0, - updateShare: 0, - }, - { - name: "Single Thread - Balanced Inserts and Deletes", - concurrency: 1, - timeForTesting: 5 * time.Second, - maxValForCol: 5, - maxValForId: 10, - insertShare: 50, - deleteShare: 50, - updateShare: 0, - }, - { - name: "Single Thread - Balanced Inserts and Updates", - concurrency: 1, - timeForTesting: 5 * time.Second, - maxValForCol: 5, - maxValForId: 10, - insertShare: 50, - deleteShare: 0, - updateShare: 50, - }, - { - name: "Single Thread - Balanced Inserts, Updates and Deletes", - concurrency: 1, - timeForTesting: 5 * time.Second, - maxValForCol: 5, - maxValForId: 10, - insertShare: 50, - deleteShare: 50, - updateShare: 50, - }, - { - name: "Multi Thread - Balanced Inserts, Updates and Deletes", - concurrency: 30, - timeForTesting: 5 * time.Second, - maxValForCol: 5, - maxValForId: 30, - insertShare: 50, - deleteShare: 50, - updateShare: 50, - }, - } + }{{ + name: "Single Thread - Only Inserts", + concurrency: 1, + timeForTesting: 5 * time.Second, + maxValForCol: 5, + maxValForId: 10, + insertShare: 100, + deleteShare: 0, + updateShare: 0, + }, { + name: "Single Thread - Balanced Inserts and Deletes", + concurrency: 1, + timeForTesting: 5 * time.Second, + maxValForCol: 5, + maxValForId: 10, + insertShare: 50, + deleteShare: 50, + updateShare: 0, + }, { + name: "Single Thread - Balanced Inserts and Updates", + concurrency: 1, + timeForTesting: 5 * time.Second, + maxValForCol: 5, + maxValForId: 10, + insertShare: 50, + deleteShare: 0, + updateShare: 50, + }, { + name: "Single Thread - Balanced Inserts, Updates and Deletes", + concurrency: 1, + timeForTesting: 5 * time.Second, + maxValForCol: 5, + maxValForId: 10, + insertShare: 50, + deleteShare: 50, + updateShare: 50, + }, { + name: "Multi Thread - Only Inserts", + concurrency: 30, + timeForTesting: 5 * time.Second, + maxValForCol: 5, + maxValForId: 30, + insertShare: 100, + deleteShare: 0, + updateShare: 0, + }, { + name: "Multi Thread - Balanced Inserts, Updates and Deletes", + concurrency: 30, + timeForTesting: 5 * time.Second, + maxValForCol: 5, + maxValForId: 30, + insertShare: 50, + deleteShare: 50, + updateShare: 50, + }} for _, tt := range testcases { for _, testSharded := range []bool{false, true} { @@ -632,7 +639,16 @@ func TestFkFuzzTest(t *testing.T) { fz.start(t, testSharded) // Wait for the timeForTesting so that the threads continue to run. - time.Sleep(tt.timeForTesting) + totalTime := time.After(tt.timeForTesting) + done := false + for !done { + select { + case <-totalTime: + done = true + case <-time.After(10 * time.Millisecond): + validateReplication(t) + } + } fz.stop() @@ -654,3 +670,15 @@ func TestFkFuzzTest(t *testing.T) { } } } + +func validateReplication(t *testing.T) { + for _, keyspace := range clusterInstance.Keyspaces { + for _, shard := range keyspace.Shards { + for _, vttablet := range shard.Vttablets { + if vttablet.Type != "primary" { + checkReplicationHealthy(t, vttablet) + } + } + } + } +} diff --git a/go/test/endtoend/vtgate/foreignkey/fk_test.go b/go/test/endtoend/vtgate/foreignkey/fk_test.go index afd04679066..a05f0de7ac1 100644 --- a/go/test/endtoend/vtgate/foreignkey/fk_test.go +++ b/go/test/endtoend/vtgate/foreignkey/fk_test.go @@ -986,3 +986,164 @@ func TestCyclicFks(t *testing.T) { _, err := utils.ExecAllowError(t, mcmp.VtConn, "insert into fk_t10(id, col) values (1, 1)") require.ErrorContains(t, err, "VT09019: uks has cyclic foreign keys") } + +func TestReplace(t *testing.T) { + t.Skip("replace engine marked for failure, hence skipping this.") + // Wait for schema-tracking to be complete. + waitForSchemaTrackingForFkTables(t) + // Remove all the foreign key constraints for all the replicas. + // We can then verify that the replica, and the primary have the same data, to ensure + // that none of the queries ever lead to cascades/updates on MySQL level. + for _, ks := range []string{shardedKs, unshardedKs} { + replicas := getReplicaTablets(ks) + for _, replica := range replicas { + removeAllForeignKeyConstraints(t, replica, ks) + } + } + + mcmp1, _ := start(t) + // defer closer1() + _ = utils.Exec(t, mcmp1.VtConn, "use `uks`") + + mcmp2, _ := start(t) + // defer closer2() + _ = utils.Exec(t, mcmp2.VtConn, "use `uks`") + + _ = utils.Exec(t, mcmp1.VtConn, "insert into fk_t2 values(1,5), (2,5)") + + done := false + go func() { + number := 1 + for !done { + query := fmt.Sprintf("replace /* g1q1 - %d */ into fk_t2 values(5,5)", number) + _, _ = utils.ExecAllowError(t, mcmp1.VtConn, query) + number++ + } + }() + + go func() { + number := 1 + for !done { + query := fmt.Sprintf("replace /* q1 - %d */ into fk_t3 values(3,5)", number) + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, query) + + query = fmt.Sprintf("replace /* q2 - %d */ into fk_t3 values(4,5)", number) + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, query) + number++ + } + }() + + totalTime := time.After(1 * time.Minute) + for !done { + select { + case <-totalTime: + done = true + case <-time.After(10 * time.Millisecond): + validateReplication(t) + } + } +} + +func TestReplaceExplicit(t *testing.T) { + t.Skip("explicit delete-insert in transaction fails, hence skipping") + // Wait for schema-tracking to be complete. + waitForSchemaTrackingForFkTables(t) + // Remove all the foreign key constraints for all the replicas. + // We can then verify that the replica, and the primary have the same data, to ensure + // that none of the queries ever lead to cascades/updates on MySQL level. + for _, ks := range []string{shardedKs, unshardedKs} { + replicas := getReplicaTablets(ks) + for _, replica := range replicas { + removeAllForeignKeyConstraints(t, replica, ks) + } + } + + mcmp1, _ := start(t) + // defer closer1() + _ = utils.Exec(t, mcmp1.VtConn, "use `uks`") + + mcmp2, _ := start(t) + // defer closer2() + _ = utils.Exec(t, mcmp2.VtConn, "use `uks`") + + _ = utils.Exec(t, mcmp1.VtConn, "insert into fk_t2 values(1,5), (2,5)") + + done := false + go func() { + number := 0 + for !done { + number++ + _, _ = utils.ExecAllowError(t, mcmp1.VtConn, "begin") + query := fmt.Sprintf("delete /* g1q1 - %d */ from fk_t2 where id = 5", number) + _, err := utils.ExecAllowError(t, mcmp1.VtConn, query) + if err != nil { + _, _ = utils.ExecAllowError(t, mcmp1.VtConn, "rollback") + continue + } + query = fmt.Sprintf("insert /* g1q1 - %d */ into fk_t2 values(5,5)", number) + _, err = utils.ExecAllowError(t, mcmp1.VtConn, query) + if err != nil { + _, _ = utils.ExecAllowError(t, mcmp1.VtConn, "rollback") + continue + } + _, _ = utils.ExecAllowError(t, mcmp1.VtConn, "commit") + } + }() + + go func() { + number := 0 + for !done { + number++ + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "begin") + query := fmt.Sprintf("delete /* g1q1 - %d */ from fk_t3 where id = 3 or col = 5", number) + _, err := utils.ExecAllowError(t, mcmp2.VtConn, query) + if err != nil { + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "rollback") + } else { + query = fmt.Sprintf("insert /* g1q1 - %d */ into fk_t3 values(3,5)", number) + _, err = utils.ExecAllowError(t, mcmp2.VtConn, query) + if err != nil { + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "rollback") + } else { + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "commit") + } + } + + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "begin") + query = fmt.Sprintf("delete /* g1q1 - %d */ from fk_t3 where id = 4 or col = 5", number) + _, err = utils.ExecAllowError(t, mcmp2.VtConn, query) + if err != nil { + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "rollback") + continue + } + query = fmt.Sprintf("insert /* g1q1 - %d */ into fk_t3 values(4,5)", number) + _, err = utils.ExecAllowError(t, mcmp2.VtConn, query) + if err != nil { + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "rollback") + continue + } + _, _ = utils.ExecAllowError(t, mcmp2.VtConn, "commit") + } + }() + + totalTime := time.After(1 * time.Minute) + for !done { + select { + case <-totalTime: + done = true + case <-time.After(10 * time.Millisecond): + validateReplication(t) + } + } +} + +// TestReplaceWithFK tests that replace into work as expected when foreign key management is enabled in Vitess. +func TestReplaceWithFK(t *testing.T) { + mcmp, closer := start(t) + conn := mcmp.VtConn + defer closer() + + // replace some data. + _, err := utils.ExecAllowError(t, conn, `replace into t1(id, col) values (1, 1)`) + require.ErrorContains(t, err, "VT12001: unsupported: REPLACE INTO with sharded keyspace (errno 1235) (sqlstate 42000)") +} diff --git a/go/test/endtoend/vtgate/foreignkey/main_test.go b/go/test/endtoend/vtgate/foreignkey/main_test.go index dae78ae93a1..3ce449f893f 100644 --- a/go/test/endtoend/vtgate/foreignkey/main_test.go +++ b/go/test/endtoend/vtgate/foreignkey/main_test.go @@ -38,11 +38,9 @@ var ( shardedKs = "ks" unshardedKs = "uks" Cell = "test" - //go:embed sharded_schema.sql - shardedSchemaSQL string - //go:embed unsharded_schema.sql - unshardedSchemaSQL string + //go:embed schema.sql + schemaSQL string //go:embed sharded_vschema.json shardedVSchema string @@ -107,7 +105,7 @@ func TestMain(m *testing.M) { // Start keyspace sKs := &cluster.Keyspace{ Name: shardedKs, - SchemaSQL: shardedSchemaSQL, + SchemaSQL: schemaSQL, VSchema: shardedVSchema, } @@ -118,7 +116,7 @@ func TestMain(m *testing.M) { uKs := &cluster.Keyspace{ Name: unshardedKs, - SchemaSQL: unshardedSchemaSQL, + SchemaSQL: schemaSQL, VSchema: unshardedVSchema, } err = clusterInstance.StartUnshardedKeyspace(*uKs, 1, false) @@ -142,7 +140,7 @@ func TestMain(m *testing.M) { } vtgateGrpcAddress = fmt.Sprintf("%s:%d", clusterInstance.Hostname, clusterInstance.VtgateGrpcPort) - connParams, closer, err := utils.NewMySQL(clusterInstance, shardedKs, shardedSchemaSQL) + connParams, closer, err := utils.NewMySQL(clusterInstance, shardedKs, schemaSQL) if err != nil { fmt.Println(err) return 1 diff --git a/go/test/endtoend/vtgate/foreignkey/sharded_schema.sql b/go/test/endtoend/vtgate/foreignkey/schema.sql similarity index 95% rename from go/test/endtoend/vtgate/foreignkey/sharded_schema.sql rename to go/test/endtoend/vtgate/foreignkey/schema.sql index c1f511350f2..fd8bec5dc4a 100644 --- a/go/test/endtoend/vtgate/foreignkey/sharded_schema.sql +++ b/go/test/endtoend/vtgate/foreignkey/schema.sql @@ -73,6 +73,31 @@ create table t6 foreign key (sk, col1) references t5 (sk, col1) on delete restrict on update restrict ) Engine = InnoDB; +create table u_t1 +( + id bigint, + col1 bigint, + index(col1), + primary key (id) +) Engine = InnoDB; + +create table u_t2 +( + id bigint, + col2 bigint, + primary key (id), + foreign key (col2) references u_t1 (col1) on delete set null on update set null +) Engine = InnoDB; + +create table u_t3 +( + id bigint, + col3 bigint, + primary key (id), + foreign key (col3) references u_t1 (col1) on delete cascade on update cascade +) Engine = InnoDB; + + /* * fk_t1 * │ @@ -122,7 +147,7 @@ create table fk_t3 id bigint, col varchar(10), primary key (id), - index(col), + unique index(col), foreign key (col) references fk_t2(col) on delete set null on update set null ) Engine = InnoDB; @@ -184,7 +209,7 @@ create table fk_t10 id bigint, col varchar(10), primary key (id), - index(col) + unique index(col) ) Engine = InnoDB; create table fk_t11 @@ -243,7 +268,7 @@ create table fk_t15 id bigint, col varchar(10), primary key (id), - index(col) + unique index(col) ) Engine = InnoDB; create table fk_t16 @@ -251,7 +276,7 @@ create table fk_t16 id bigint, col varchar(10), primary key (id), - index(col), + unique index(col), foreign key (col) references fk_t15(col) on delete cascade on update cascade ) Engine = InnoDB; @@ -296,6 +321,7 @@ create table fk_t20 foreign key (col2) references fk_t20(col) on delete restrict on update restrict ) Engine = InnoDB; + /* * fk_multicol_t1 * │ @@ -328,16 +354,17 @@ create table fk_multicol_t1 colb varchar(10), cola varchar(10), primary key (id), - index(cola, colb) + index(cola, colb), + unique index(colb) ) Engine = InnoDB; create table fk_multicol_t2 ( id bigint, - colb varchar(10), + colb varchar(10) default 'xyz', cola varchar(10), primary key (id), - index(cola, colb), + unique index(cola, colb), foreign key (cola, colb) references fk_multicol_t1(cola, colb) on delete restrict on update restrict ) Engine = InnoDB; @@ -355,9 +382,10 @@ create table fk_multicol_t4 ( id bigint, colb varchar(10), - cola varchar(10), + cola varchar(10) default 'abcd', primary key (id), index(cola, colb), + unique index(cola), foreign key (cola, colb) references fk_multicol_t3(cola, colb) on delete set null on update set null ) Engine = InnoDB; diff --git a/go/test/endtoend/vtgate/foreignkey/unsharded_schema.sql b/go/test/endtoend/vtgate/foreignkey/unsharded_schema.sql deleted file mode 100644 index 3b4496d47fb..00000000000 --- a/go/test/endtoend/vtgate/foreignkey/unsharded_schema.sql +++ /dev/null @@ -1,472 +0,0 @@ -create table u_t1 -( - id bigint, - col1 bigint, - index(col1), - primary key (id) -) Engine = InnoDB; - -create table u_t2 -( - id bigint, - col2 bigint, - primary key (id), - foreign key (col2) references u_t1 (col1) on delete set null on update set null -) Engine = InnoDB; - -create table u_t3 -( - id bigint, - col3 bigint, - primary key (id), - foreign key (col3) references u_t1 (col1) on delete cascade on update cascade -) Engine = InnoDB; - - -/* - * fk_t1 - * │ - * │ On Delete Restrict - * │ On Update Restrict - * ▼ - * ┌────────────────fk_t2────────────────┐ - * │ │ - * │On Delete Set Null │ On Delete Set Null - * │On Update Set Null │ On Update Set Null - * ▼ ▼ - * fk_t7 fk_t3───────────────────┐ - * │ │ - * │ │ On Delete Set Null - * On Delete Set Null │ │ On Update Set Null - * On Update Set Null │ │ - * ▼ ▼ - * fk_t4 fk_t6 - * │ - * │ - * On Delete Restrict │ - * On Update Restrict │ - * │ - * ▼ - * fk_t5 - */ - -create table fk_t1 -( - id bigint, - col varchar(10), - primary key (id), - index(col) -) Engine = InnoDB; - -create table fk_t2 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t1(col) on delete restrict on update restrict -) Engine = InnoDB; - -create table fk_t3 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t2(col) on delete set null on update set null -) Engine = InnoDB; - -create table fk_t4 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t3(col) on delete set null on update set null -) Engine = InnoDB; - -create table fk_t5 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t4(col) on delete restrict on update restrict -) Engine = InnoDB; - -create table fk_t6 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t3(col) on delete set null on update set null -) Engine = InnoDB; - -create table fk_t7 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t2(col) on delete set null on update set null -) Engine = InnoDB; - -/* - * fk_t10 - * │ - * On Delete Cascade │ - * On Update Cascade │ - * │ - * ▼ - * fk_t11──────────────────┐ - * │ │ - * │ │ On Delete Restrict - * On Delete Cascade │ │ On Update Restrict - * On Update Cascade │ │ - * │ │ - * ▼ ▼ - * fk_t12 fk_t13 - */ - -create table fk_t10 -( - id bigint, - col varchar(10), - primary key (id), - index(col) -) Engine = InnoDB; - -create table fk_t11 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t10(col) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_t12 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t11(col) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_t13 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t11(col) on delete restrict on update restrict -) Engine = InnoDB; - -/* - * fk_t15 - * │ - * │ - * On Delete Cascade │ - * On Update Cascade │ - * │ - * ▼ - * fk_t16 - * │ - * On Delete Set Null │ - * On Update Set Null │ - * │ - * ▼ - * fk_t17──────────────────┐ - * │ │ - * │ │ On Delete Set Null - * On Delete Cascade │ │ On Update Set Null - * On Update Cascade │ │ - * │ │ - * ▼ ▼ - * fk_t18 fk_t19 - */ - -create table fk_t15 -( - id bigint, - col varchar(10), - primary key (id), - index(col) -) Engine = InnoDB; - -create table fk_t16 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t15(col) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_t17 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t16(col) on delete set null on update set null -) Engine = InnoDB; - -create table fk_t18 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t17(col) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_t19 -( - id bigint, - col varchar(10), - primary key (id), - index(col), - foreign key (col) references fk_t17(col) on delete set null on update set null -) Engine = InnoDB; - -/* - Self referenced foreign key from col2 to col in fk_t20 -*/ - -create table fk_t20 -( - id bigint, - col varchar(10), - col2 varchar(10), - primary key (id), - index(col), - foreign key (col2) references fk_t20(col) on delete restrict on update restrict -) Engine = InnoDB; - - -/* - * fk_multicol_t1 - * │ - * │ On Delete Restrict - * │ On Update Restrict - * ▼ - * ┌────────fk_multicol_t2───────────────┐ - * │ │ - * │On Delete Set Null │ On Delete Set Null - * │On Update Set Null │ On Update Set Null - * ▼ ▼ - * fk_multicol_t7 fk_multicol_t3───────────────────┐ - * │ │ - * │ │ On Delete Set Null - * On Delete Set Null │ │ On Update Set Null - * On Update Set Null │ │ - * ▼ ▼ - * fk_multicol_t4 fk_multicol_t6 - * │ - * │ - * On Delete Restrict │ - * On Update Restrict │ - * │ - * ▼ - * fk_multicol_t5 - */ -create table fk_multicol_t1 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb) -) Engine = InnoDB; - -create table fk_multicol_t2 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t1(cola, colb) on delete restrict on update restrict -) Engine = InnoDB; - -create table fk_multicol_t3 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t2(cola, colb) on delete set null on update set null -) Engine = InnoDB; - -create table fk_multicol_t4 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t3(cola, colb) on delete set null on update set null -) Engine = InnoDB; - -create table fk_multicol_t5 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t4(cola, colb) on delete restrict on update restrict -) Engine = InnoDB; - -create table fk_multicol_t6 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t3(cola, colb) on delete set null on update set null -) Engine = InnoDB; - -create table fk_multicol_t7 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t2(cola, colb) on delete set null on update set null -) Engine = InnoDB; - -/* - * fk_multicol_t10 - * │ - * On Delete Cascade │ - * On Update Cascade │ - * │ - * ▼ - * fk_multicol_t11──────────────────┐ - * │ │ - * │ │ On Delete Restrict - * On Delete Cascade │ │ On Update Restrict - * On Update Cascade │ │ - * │ │ - * ▼ ▼ - * fk_multicol_t12 fk_multicol_t13 - */ - -create table fk_multicol_t10 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb) -) Engine = InnoDB; - -create table fk_multicol_t11 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t10(cola, colb) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_multicol_t12 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t11(cola, colb) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_multicol_t13 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t11(cola, colb) on delete restrict on update restrict -) Engine = InnoDB; - -/* - * fk_multicol_t15 - * │ - * │ - * On Delete Cascade │ - * On Update Cascade │ - * │ - * ▼ - * fk_multicol_t16 - * │ - * On Delete Set Null │ - * On Update Set Null │ - * │ - * ▼ - * fk_multicol_t17──────────────────┐ - * │ │ - * │ │ On Delete Set Null - * On Delete Cascade │ │ On Update Set Null - * On Update Cascade │ │ - * │ │ - * ▼ ▼ - * fk_multicol_t18 fk_multicol_t19 - */ - -create table fk_multicol_t15 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb) -) Engine = InnoDB; - -create table fk_multicol_t16 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t15(cola, colb) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_multicol_t17 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t16(cola, colb) on delete set null on update set null -) Engine = InnoDB; - -create table fk_multicol_t18 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t17(cola, colb) on delete cascade on update cascade -) Engine = InnoDB; - -create table fk_multicol_t19 -( - id bigint, - colb varchar(10), - cola varchar(10), - primary key (id), - index(cola, colb), - foreign key (cola, colb) references fk_multicol_t17(cola, colb) on delete set null on update set null -) Engine = InnoDB; diff --git a/go/vt/proto/vschema/vschema.pb.go b/go/vt/proto/vschema/vschema.pb.go index b45f7010c56..fe185d63fcd 100644 --- a/go/vt/proto/vschema/vschema.pb.go +++ b/go/vt/proto/vschema/vschema.pb.go @@ -603,6 +603,7 @@ type Column struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Type query.Type `protobuf:"varint,2,opt,name=type,proto3,enum=query.Type" json:"type,omitempty"` Invisible bool `protobuf:"varint,3,opt,name=invisible,proto3" json:"invisible,omitempty"` + Default string `protobuf:"bytes,4,opt,name=default,proto3" json:"default,omitempty"` } func (x *Column) Reset() { @@ -658,6 +659,13 @@ func (x *Column) GetInvisible() bool { return false } +func (x *Column) GetDefault() string { + if x != nil { + return x.Default + } + return "" +} + // SrvVSchema is the roll-up of all the Keyspace schema for a cell. type SrvVSchema struct { state protoimpl.MessageState @@ -920,46 +928,47 @@ var file_vschema_proto_rawDesc = []byte{ 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, - 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x5b, 0x0a, 0x06, 0x43, 0x6f, 0x6c, 0x75, 0x6d, + 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x22, 0x75, 0x0a, 0x06, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0b, 0x2e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x76, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x6e, 0x76, 0x69, 0x73, - 0x69, 0x62, 0x6c, 0x65, 0x22, 0xa7, 0x02, 0x0a, 0x0a, 0x53, 0x72, 0x76, 0x56, 0x53, 0x63, 0x68, - 0x65, 0x6d, 0x61, 0x12, 0x40, 0x0a, 0x09, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x2e, 0x53, 0x72, 0x76, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, - 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x76, - 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, - 0x6c, 0x65, 0x73, 0x52, 0x0c, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, - 0x73, 0x12, 0x4a, 0x0a, 0x13, 0x73, 0x68, 0x61, 0x72, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x69, - 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x6f, - 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x11, 0x73, 0x68, 0x61, 0x72, - 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x4f, 0x0a, - 0x0e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x11, 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x44, - 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, - 0x6c, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x68, 0x61, - 0x72, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, - 0x75, 0x6c, 0x65, 0x73, 0x22, 0x6e, 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x75, - 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x6d, - 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1f, 0x0a, - 0x0b, 0x74, 0x6f, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x74, 0x6f, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, - 0x68, 0x61, 0x72, 0x64, 0x42, 0x26, 0x5a, 0x24, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2e, 0x69, - 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0xa7, + 0x02, 0x0a, 0x0a, 0x53, 0x72, 0x76, 0x56, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12, 0x40, 0x0a, + 0x09, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x72, 0x76, 0x56, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x6b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x73, 0x12, + 0x3a, 0x0a, 0x0d, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x0c, 0x72, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x13, 0x73, + 0x68, 0x61, 0x72, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, + 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x76, 0x73, 0x63, 0x68, 0x65, + 0x6d, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, + 0x75, 0x6c, 0x65, 0x73, 0x52, 0x11, 0x73, 0x68, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, + 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x4f, 0x0a, 0x0e, 0x4b, 0x65, 0x79, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x27, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x76, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x44, 0x0a, 0x11, 0x53, 0x68, 0x61, 0x72, + 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x2f, 0x0a, + 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x76, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x6e, + 0x0a, 0x10, 0x53, 0x68, 0x61, 0x72, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, + 0x6c, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x72, 0x6f, 0x6d, 0x4b, + 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x5f, 0x6b, 0x65, + 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x6f, + 0x4b, 0x65, 0x79, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x42, 0x26, + 0x5a, 0x24, 0x76, 0x69, 0x74, 0x65, 0x73, 0x73, 0x2e, 0x69, 0x6f, 0x2f, 0x76, 0x69, 0x74, 0x65, + 0x73, 0x73, 0x2f, 0x67, 0x6f, 0x2f, 0x76, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x76, + 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/go/vt/proto/vschema/vschema_vtproto.pb.go b/go/vt/proto/vschema/vschema_vtproto.pb.go index 94527ef14ae..2235bfea496 100644 --- a/go/vt/proto/vschema/vschema_vtproto.pb.go +++ b/go/vt/proto/vschema/vschema_vtproto.pb.go @@ -213,6 +213,7 @@ func (m *Column) CloneVT() *Column { Name: m.Name, Type: m.Type, Invisible: m.Invisible, + Default: m.Default, } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) @@ -787,6 +788,13 @@ func (m *Column) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.Default) > 0 { + i -= len(m.Default) + copy(dAtA[i:], m.Default) + i = encodeVarint(dAtA, i, uint64(len(m.Default))) + i-- + dAtA[i] = 0x22 + } if m.Invisible { i-- if m.Invisible { @@ -1203,6 +1211,10 @@ func (m *Column) SizeVT() (n int) { if m.Invisible { n += 2 } + l = len(m.Default) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } n += len(m.unknownFields) return n } @@ -2725,6 +2737,38 @@ func (m *Column) UnmarshalVT(dAtA []byte) error { } } m.Invisible = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Default", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Default = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/go/vt/vterrors/code.go b/go/vt/vterrors/code.go index 6bc317db4ed..80e1b0edd34 100644 --- a/go/vt/vterrors/code.go +++ b/go/vt/vterrors/code.go @@ -36,7 +36,7 @@ var ( VT03011 = errorWithoutState("VT03011", vtrpcpb.Code_INVALID_ARGUMENT, "invalid value type: %v", "The given value type is not accepted.") VT03012 = errorWithoutState("VT03012", vtrpcpb.Code_INVALID_ARGUMENT, "invalid syntax: %s", "The syntax is invalid. Please refer to the MySQL documentation for the proper syntax.") VT03013 = errorWithState("VT03013", vtrpcpb.Code_INVALID_ARGUMENT, NonUniqTable, "not unique table/alias: '%s'", "This table or alias name is already use. Please use another one that is unique.") - VT03014 = errorWithState("VT03014", vtrpcpb.Code_INVALID_ARGUMENT, BadFieldError, "unknown column '%d' in '%s'", "The given column is unknown.") + VT03014 = errorWithState("VT03014", vtrpcpb.Code_INVALID_ARGUMENT, BadFieldError, "unknown column '%s' in '%s'", "The given column is unknown.") VT03015 = errorWithoutState("VT03015", vtrpcpb.Code_INVALID_ARGUMENT, "column has duplicate set values: '%v'", "Cannot assign multiple values to a column in an update statement.") VT03016 = errorWithoutState("VT03016", vtrpcpb.Code_INVALID_ARGUMENT, "unknown vindex column: '%s'", "The given column is unknown in the vindex table.") VT03017 = errorWithState("VT03017", vtrpcpb.Code_INVALID_ARGUMENT, SyntaxError, "where clause can only be of the type 'pos > '", "This vstream where clause can only be a greater than filter.") @@ -49,6 +49,7 @@ var ( VT03024 = errorWithoutState("VT03024", vtrpcpb.Code_INVALID_ARGUMENT, "'%s' user defined variable does not exists", "The query cannot be prepared using the user defined variable as it does not exists for this session.") VT03025 = errorWithState("VT03025", vtrpcpb.Code_INVALID_ARGUMENT, WrongArguments, "Incorrect arguments to %s", "The execute statement have wrong number of arguments") VT03026 = errorWithoutState("VT03024", vtrpcpb.Code_INVALID_ARGUMENT, "'%s' bind variable does not exists", "The query cannot be executed as missing the bind variable.") + VT03027 = errorWithState("VT03027", vtrpcpb.Code_INVALID_ARGUMENT, BadNullError, "Column '%s' cannot be null", "The column cannot have null value.") VT05001 = errorWithState("VT05001", vtrpcpb.Code_NOT_FOUND, DbDropExists, "cannot drop database '%s'; database does not exists", "The given database does not exist; Vitess cannot drop it.") VT05002 = errorWithState("VT05002", vtrpcpb.Code_NOT_FOUND, BadDb, "cannot alter database '%s'; unknown database", "The given database does not exist; Vitess cannot alter it.") @@ -83,6 +84,7 @@ var ( VT09019 = errorWithoutState("VT09019", vtrpcpb.Code_FAILED_PRECONDITION, "%s has cyclic foreign keys", "Vitess doesn't support cyclic foreign keys.") VT10001 = errorWithoutState("VT10001", vtrpcpb.Code_ABORTED, "foreign key constraints are not allowed", "Foreign key constraints are not allowed, see https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/.") + VT10002 = errorWithoutState("VT10002", vtrpcpb.Code_ABORTED, "'replace into' with foreign key constraints are not allowed", "Foreign key constraints sometimes are not written in binary logs and will cause issue with vreplication workflows like online-ddl.") VT12001 = errorWithoutState("VT12001", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: %s", "This statement is unsupported by Vitess. Please rewrite your query to use supported syntax.") VT12002 = errorWithoutState("VT12002", vtrpcpb.Code_UNIMPLEMENTED, "unsupported: cross-shard foreign keys", "Vitess does not support cross shard foreign keys.") @@ -124,6 +126,7 @@ var ( VT03024, VT03025, VT03026, + VT03027, VT05001, VT05002, VT05003, @@ -152,6 +155,7 @@ var ( VT09017, VT09018, VT10001, + VT10002, VT12001, VT12002, VT13001, diff --git a/go/vt/vterrors/state.go b/go/vt/vterrors/state.go index 5e3dcf22dfb..5d286b0c991 100644 --- a/go/vt/vterrors/state.go +++ b/go/vt/vterrors/state.go @@ -47,6 +47,7 @@ const ( WrongValueCountOnRow WrongValue WrongArguments + BadNullError // failed precondition NoDB diff --git a/go/vt/vtgate/engine/cached_size.go b/go/vt/vtgate/engine/cached_size.go index d878650c63e..6121ad5f675 100644 --- a/go/vt/vtgate/engine/cached_size.go +++ b/go/vt/vtgate/engine/cached_size.go @@ -145,7 +145,7 @@ func (cached *DML) CachedSize(alloc bool) int64 { } size := int64(0) if alloc { - size += int64(128) + size += int64(144) } // field Query string size += hack.RuntimeAllocSize(int64(len(cached.Query))) @@ -1007,6 +1007,25 @@ func (cached *Send) CachedSize(alloc bool) int64 { size += hack.RuntimeAllocSize(int64(len(cached.Query))) return size } +func (cached *Sequential) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(24) + } + // field Sources []vitess.io/vitess/go/vt/vtgate/engine.Primitive + { + size += hack.RuntimeAllocSize(int64(cap(cached.Sources)) * int64(16)) + for _, elem := range cached.Sources { + if cc, ok := elem.(cachedObject); ok { + size += cc.CachedSize(true) + } + } + } + return size +} func (cached *SessionPrimitive) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) diff --git a/go/vt/vtgate/engine/delete.go b/go/vt/vtgate/engine/delete.go index 5f0f2408993..adcc11174fd 100644 --- a/go/vt/vtgate/engine/delete.go +++ b/go/vt/vtgate/engine/delete.go @@ -130,6 +130,7 @@ func (del *Delete) description() PrimitiveDescription { "OwnedVindexQuery": del.OwnedVindexQuery, "MultiShardAutocommit": del.MultiShardAutocommit, "QueryTimeout": del.QueryTimeout, + "NoAutoCommit": del.PreventAutoCommit, } addFieldsIfNotEmpty(del.DML, other) diff --git a/go/vt/vtgate/engine/dml.go b/go/vt/vtgate/engine/dml.go index 51177f41e08..a7b1712bacd 100644 --- a/go/vt/vtgate/engine/dml.go +++ b/go/vt/vtgate/engine/dml.go @@ -61,6 +61,8 @@ type DML struct { // QueryTimeout contains the optional timeout (in milliseconds) to apply to this query QueryTimeout int + PreventAutoCommit bool + // RoutingParameters parameters required for query routing. *RoutingParameters @@ -73,7 +75,7 @@ func NewDML() *DML { } func (dml *DML) execUnsharded(ctx context.Context, primitive Primitive, vcursor VCursor, bindVars map[string]*querypb.BindVariable, rss []*srvtopo.ResolvedShard) (*sqltypes.Result, error) { - return execShard(ctx, primitive, vcursor, dml.Query, bindVars, rss[0], true /* rollbackOnError */, true /* canAutocommit */) + return execShard(ctx, primitive, vcursor, dml.Query, bindVars, rss[0], true /* rollbackOnError */, !dml.PreventAutoCommit /* canAutocommit */) } func (dml *DML) execMultiDestination(ctx context.Context, primitive Primitive, vcursor VCursor, bindVars map[string]*querypb.BindVariable, rss []*srvtopo.ResolvedShard, dmlSpecialFunc func(context.Context, VCursor, map[string]*querypb.BindVariable, []*srvtopo.ResolvedShard) error) (*sqltypes.Result, error) { diff --git a/go/vt/vtgate/engine/insert.go b/go/vt/vtgate/engine/insert.go index fc65fbfbff9..e0c90d091a0 100644 --- a/go/vt/vtgate/engine/insert.go +++ b/go/vt/vtgate/engine/insert.go @@ -102,6 +102,8 @@ type ( // This will avoid locking by the select table. ForceNonStreaming bool + PreventAutoCommit bool + // Insert needs tx handling txNeeded } @@ -958,6 +960,7 @@ func (ins *Insert) description() PrimitiveDescription { "QueryTimeout": ins.QueryTimeout, "InsertIgnore": ins.Ignore, "InputAsNonStreaming": ins.ForceNonStreaming, + "NoAutoCommit": ins.PreventAutoCommit, } if len(ins.VindexValues) > 0 { @@ -1041,7 +1044,7 @@ func (ins *Insert) executeUnshardedTableQuery(ctx context.Context, vcursor VCurs if err != nil { return 0, nil, err } - qr, err := execShard(ctx, ins, vcursor, query, bindVars, rss[0], true, true /* canAutocommit */) + qr, err := execShard(ctx, ins, vcursor, query, bindVars, rss[0], true, !ins.PreventAutoCommit /* canAutocommit */) if err != nil { return 0, nil, err } diff --git a/go/vt/vtgate/engine/sequential.go b/go/vt/vtgate/engine/sequential.go new file mode 100644 index 00000000000..d038df4afbf --- /dev/null +++ b/go/vt/vtgate/engine/sequential.go @@ -0,0 +1,90 @@ +/* +Copyright 2023 The Vitess Authors. + +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 engine + +import ( + "context" + + "vitess.io/vitess/go/sqltypes" + querypb "vitess.io/vitess/go/vt/proto/query" + vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" + "vitess.io/vitess/go/vt/vterrors" +) + +// Sequential Primitive is used to execute DML statements in a fixed order. +// Any failure, stops the execution and returns. +type Sequential struct { + Sources []Primitive + + txNeeded +} + +var _ Primitive = (*Sequential)(nil) + +// NewSequential creates a Sequential primitive. +func NewSequential(Sources []Primitive) *Sequential { + return &Sequential{ + Sources: Sources, + } +} + +// RouteType returns a description of the query routing type used by the primitive +func (s *Sequential) RouteType() string { + return "Sequential" +} + +// GetKeyspaceName specifies the Keyspace that this primitive routes to +func (s *Sequential) GetKeyspaceName() string { + res := s.Sources[0].GetKeyspaceName() + for i := 1; i < len(s.Sources); i++ { + res = formatTwoOptionsNicely(res, s.Sources[i].GetKeyspaceName()) + } + return res +} + +// GetTableName specifies the table that this primitive routes to. +func (s *Sequential) GetTableName() string { + res := s.Sources[0].GetTableName() + for i := 1; i < len(s.Sources); i++ { + res = formatTwoOptionsNicely(res, s.Sources[i].GetTableName()) + } + return res +} + +// TryExecute performs a non-streaming exec. +func (s *Sequential) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantFields bool) (*sqltypes.Result, error) { + return nil, vterrors.VT10002() +} + +// TryStreamExecute performs a streaming exec. +func (s *Sequential) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantFields bool, callback func(*sqltypes.Result) error) error { + return vterrors.VT10002() +} + +// GetFields fetches the field info. +func (s *Sequential) GetFields(context.Context, VCursor, map[string]*querypb.BindVariable) (*sqltypes.Result, error) { + return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "[BUG] unreachable code for Sequential engine") +} + +// Inputs returns the input primitives for this +func (s *Sequential) Inputs() ([]Primitive, []map[string]any) { + return s.Sources, nil +} + +func (s *Sequential) description() PrimitiveDescription { + return PrimitiveDescription{OperatorType: s.RouteType()} +} diff --git a/go/vt/vtgate/engine/update.go b/go/vt/vtgate/engine/update.go index 3db7972fba5..913cacaaba8 100644 --- a/go/vt/vtgate/engine/update.go +++ b/go/vt/vtgate/engine/update.go @@ -204,6 +204,7 @@ func (upd *Update) description() PrimitiveDescription { "OwnedVindexQuery": upd.OwnedVindexQuery, "MultiShardAutocommit": upd.MultiShardAutocommit, "QueryTimeout": upd.QueryTimeout, + "NoAutoCommit": upd.PreventAutoCommit, } addFieldsIfNotEmpty(upd.DML, other) diff --git a/go/vt/vtgate/planbuilder/insert.go b/go/vt/vtgate/planbuilder/insert.go index c187cd7efdc..39144fc858d 100644 --- a/go/vt/vtgate/planbuilder/insert.go +++ b/go/vt/vtgate/planbuilder/insert.go @@ -19,7 +19,6 @@ package planbuilder import ( querypb "vitess.io/vitess/go/vt/proto/query" "vitess.io/vitess/go/vt/sqlparser" - "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/engine" "vitess.io/vitess/go/vt/vtgate/planbuilder/operators" "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" @@ -62,7 +61,7 @@ func gen4InsertStmtPlanner(version querypb.ExecuteOptions_PlannerVersion, insStm return nil, err } - if err = errOutIfPlanCannotBeConstructed(ctx, tblInfo.GetVindexTable(), insStmt, ctx.SemTable.ForeignKeysPresent()); err != nil { + if err = errOutIfPlanCannotBeConstructed(ctx, tblInfo.GetVindexTable()); err != nil { return nil, err } @@ -84,17 +83,11 @@ func gen4InsertStmtPlanner(version querypb.ExecuteOptions_PlannerVersion, insStm return newPlanResult(plan.Primitive(), operators.TablesUsed(op)...), nil } -func errOutIfPlanCannotBeConstructed(ctx *plancontext.PlanningContext, vTbl *vindexes.Table, insStmt *sqlparser.Insert, fkPlanNeeded bool) error { - if vTbl.Keyspace.Sharded && ctx.SemTable.NotUnshardedErr != nil { - return ctx.SemTable.NotUnshardedErr - } - if insStmt.Action != sqlparser.ReplaceAct { +func errOutIfPlanCannotBeConstructed(ctx *plancontext.PlanningContext, vTbl *vindexes.Table) error { + if !vTbl.Keyspace.Sharded { return nil } - if fkPlanNeeded { - return vterrors.VT12001("REPLACE INTO with foreign keys") - } - return nil + return ctx.SemTable.NotUnshardedErr } func insertUnshardedShortcut(stmt *sqlparser.Insert, ks *vindexes.Keyspace, tables []*vindexes.Table) logicalPlan { diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index 59200e92e2a..5f59a96000a 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -68,11 +68,30 @@ func transformToLogicalPlan(ctx *plancontext.PlanningContext, op ops.Operator) ( return transformFkVerify(ctx, op) case *operators.InsertSelection: return transformInsertionSelection(ctx, op) + case *operators.Sequential: + return transformSequential(ctx, op) } return nil, vterrors.VT13001(fmt.Sprintf("unknown type encountered: %T (transformToLogicalPlan)", op)) } +func transformSequential(ctx *plancontext.PlanningContext, op *operators.Sequential) (logicalPlan, error) { + var lps []logicalPlan + for _, source := range op.Sources { + lp, err := transformToLogicalPlan(ctx, source) + if err != nil { + return nil, err + } + if ins, ok := lp.(*insert); ok { + ins.eInsert.PreventAutoCommit = true + } + lps = append(lps, lp) + } + return &sequential{ + sources: lps, + }, nil +} + func transformInsertionSelection(ctx *plancontext.PlanningContext, op *operators.InsertSelection) (logicalPlan, error) { rb, isRoute := op.Insert.(*operators.Route) if !isRoute { diff --git a/go/vt/vtgate/planbuilder/operators/insert.go b/go/vt/vtgate/planbuilder/operators/insert.go index a48e53c18b1..e3f0748b78b 100644 --- a/go/vt/vtgate/planbuilder/operators/insert.go +++ b/go/vt/vtgate/planbuilder/operators/insert.go @@ -111,12 +111,228 @@ func createOperatorFromInsert(ctx *plancontext.PlanningContext, ins *sqlparser.I return nil, err } - vindexTable, routing, err := buildVindexTableForDML(ctx, tableInfo, qt, "insert") + vTbl, routing, err := buildVindexTableForDML(ctx, tableInfo, qt, "insert") if err != nil { return nil, err } - insOp, err := createInsertOperator(ctx, ins, vindexTable, routing) + deleteBeforeInsert := false + if ins.Action == sqlparser.ReplaceAct && + (ctx.SemTable.ForeignKeysPresent() || vTbl.Keyspace.Sharded) && + (len(vTbl.PrimaryKey) > 0 || len(vTbl.UniqueKeys) > 0) { + // this needs a delete before insert as there can be row clash which needs to be deleted first. + ins.Action = sqlparser.InsertAct + deleteBeforeInsert = true + } + + insOp, err := checkAndCreateInsertOperator(ctx, ins, vTbl, routing) + if err != nil { + return nil, err + } + + if !deleteBeforeInsert { + return insOp, nil + } + + rows, isRows := ins.Rows.(sqlparser.Values) + if !isRows { + return nil, vterrors.VT12001("REPLACE INTO using select statement") + } + + pkCompExpr := pkCompExpression(vTbl, ins, rows) + uniqKeyCompExprs, err := uniqKeyCompExpressions(vTbl, ins, rows) + if err != nil { + return nil, err + } + + whereExpr := getWhereCondExpr(append(uniqKeyCompExprs, pkCompExpr)) + + delStmt := &sqlparser.Delete{ + TableExprs: sqlparser.TableExprs{sqlparser.CloneRefOfAliasedTableExpr(ins.Table)}, + Where: sqlparser.NewWhere(sqlparser.WhereClause, whereExpr), + } + delOp, err := createOpFromStmt(ctx, delStmt, false, "") + if err != nil { + return nil, err + } + return &Sequential{Sources: []ops.Operator{delOp, insOp}}, nil +} + +func getWhereCondExpr(compExprs []*sqlparser.ComparisonExpr) sqlparser.Expr { + var outputExpr sqlparser.Expr + for _, expr := range compExprs { + if expr == nil { + continue + } + if outputExpr == nil { + outputExpr = expr + continue + } + outputExpr = &sqlparser.OrExpr{ + Left: outputExpr, + Right: expr, + } + } + return outputExpr +} + +func pkCompExpression(vTbl *vindexes.Table, ins *sqlparser.Insert, rows sqlparser.Values) *sqlparser.ComparisonExpr { + if len(vTbl.PrimaryKey) == 0 { + return nil + } + type pComp struct { + idx int + def sqlparser.Expr + } + var pIndexes []pComp + var pColTuple sqlparser.ValTuple + for _, pCol := range vTbl.PrimaryKey { + var def sqlparser.Expr + idx := ins.Columns.FindColumn(pCol) + if idx == -1 { + def = findDefault(vTbl, pCol) + if def == nil { + // If default value is empty, nothing to compare as it will always be false. + return nil + } + } + pIndexes = append(pIndexes, pComp{idx, def}) + pColTuple = append(pColTuple, sqlparser.NewColName(pCol.String())) + } + + var pValTuple sqlparser.ValTuple + for _, row := range rows { + var rowTuple sqlparser.ValTuple + for _, pIdx := range pIndexes { + if pIdx.idx == -1 { + rowTuple = append(rowTuple, pIdx.def) + } else { + rowTuple = append(rowTuple, row[pIdx.idx]) + } + } + pValTuple = append(pValTuple, rowTuple) + } + return sqlparser.NewComparisonExpr(sqlparser.InOp, pColTuple, pValTuple, nil) +} + +func findDefault(vTbl *vindexes.Table, pCol sqlparser.IdentifierCI) sqlparser.Expr { + for _, column := range vTbl.Columns { + if column.Name.Equal(pCol) { + return column.Default + } + } + panic(vterrors.VT03014(pCol.String(), vTbl.Name.String())) +} + +type uComp struct { + idx int + def sqlparser.Expr +} + +func uniqKeyCompExpressions(vTbl *vindexes.Table, ins *sqlparser.Insert, rows sqlparser.Values) (comps []*sqlparser.ComparisonExpr, err error) { + noOfUniqKeys := len(vTbl.UniqueKeys) + if noOfUniqKeys == 0 { + return nil, nil + } + + type uIdx struct { + Indexes [][]uComp + uniqKey sqlparser.Exprs + } + + allIndexes := make([]uIdx, 0, noOfUniqKeys) + allColTuples := make([]sqlparser.ValTuple, 0, noOfUniqKeys) + for _, uniqKey := range vTbl.UniqueKeys { + var uIndexes [][]uComp + var uColTuple sqlparser.ValTuple + skipKey := false + for _, expr := range uniqKey { + var offsets []uComp + offsets, skipKey, err = createUniqueKeyComp(ins, expr, vTbl) + if err != nil { + return nil, err + } + if skipKey { + break + } + uIndexes = append(uIndexes, offsets) + uColTuple = append(uColTuple, expr) + } + if skipKey { + continue + } + allIndexes = append(allIndexes, uIdx{uIndexes, uniqKey}) + allColTuples = append(allColTuples, uColTuple) + } + + allValTuples := make([]sqlparser.ValTuple, len(allColTuples)) + for _, row := range rows { + for i, uk := range allIndexes { + var rowTuple sqlparser.ValTuple + for j, offsets := range uk.Indexes { + colIdx := 0 + valExpr := sqlparser.CopyOnRewrite(uk.uniqKey[j], nil, func(cursor *sqlparser.CopyOnWriteCursor) { + _, isCol := cursor.Node().(*sqlparser.ColName) + if !isCol { + return + } + if offsets[colIdx].idx == -1 { + cursor.Replace(offsets[colIdx].def) + } else { + cursor.Replace(row[offsets[colIdx].idx]) + } + colIdx++ + }, nil).(sqlparser.Expr) + rowTuple = append(rowTuple, valExpr) + } + allValTuples[i] = append(allValTuples[i], rowTuple) + } + } + + compExprs := make([]*sqlparser.ComparisonExpr, 0, noOfUniqKeys) + for i, valTuple := range allValTuples { + compExprs = append(compExprs, sqlparser.NewComparisonExpr(sqlparser.InOp, allColTuples[i], valTuple, nil)) + } + return compExprs, nil +} + +func createUniqueKeyComp(ins *sqlparser.Insert, expr sqlparser.Expr, vTbl *vindexes.Table) ([]uComp, bool, error) { + col, isCol := expr.(*sqlparser.ColName) + if isCol { + var def sqlparser.Expr + idx := ins.Columns.FindColumn(col.Name) + if idx == -1 { + def = findDefault(vTbl, col.Name) + if def == nil { + // default value is empty, nothing to compare as it will always be false. + return nil, true, nil + } + } + return []uComp{{idx, def}}, false, nil + } + var offsets []uComp + err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { + col, ok := node.(*sqlparser.ColName) + if !ok { + return true, nil + } + var def sqlparser.Expr + idx := ins.Columns.FindColumn(col.Name) + if idx == -1 { + def = findDefault(vTbl, col.Name) + // no default, replace it with null value. + if def == nil { + def = &sqlparser.NullVal{} + } + } + offsets = append(offsets, uComp{idx, def}) + return false, nil + }, expr) + return offsets, false, err +} + +func checkAndCreateInsertOperator(ctx *plancontext.PlanningContext, ins *sqlparser.Insert, vTbl *vindexes.Table, routing Routing) (ops.Operator, error) { + insOp, err := createInsertOperator(ctx, ins, vTbl, routing) if err != nil { return nil, err } @@ -129,7 +345,7 @@ func createOperatorFromInsert(ctx *plancontext.PlanningContext, ins *sqlparser.I } // Find the foreign key mode and for unmanaged foreign-key-mode, we don't need to do anything. - ksMode, err := ctx.VSchema.ForeignKeyMode(vindexTable.Keyspace.Name) + ksMode, err := ctx.VSchema.ForeignKeyMode(vTbl.Keyspace.Name) if err != nil { return nil, err } @@ -139,13 +355,18 @@ func createOperatorFromInsert(ctx *plancontext.PlanningContext, ins *sqlparser.I parentFKs := ctx.SemTable.GetParentForeignKeysList() childFks := ctx.SemTable.GetChildForeignKeysList() - if len(childFks) == 0 && len(parentFKs) == 0 { - return insOp, nil - } if len(parentFKs) > 0 { return nil, vterrors.VT12002() } - return nil, vterrors.VT12001("ON DUPLICATE KEY UPDATE with foreign keys") + if len(childFks) > 0 { + if ins.Action == sqlparser.ReplaceAct { + return nil, vterrors.VT12001("REPLACE INTO with foreign keys") + } + if len(ins.OnDup) > 0 { + return nil, vterrors.VT12001("ON DUPLICATE KEY UPDATE with foreign keys") + } + } + return insOp, nil } func createInsertOperator(ctx *plancontext.PlanningContext, insStmt *sqlparser.Insert, vTbl *vindexes.Table, routing Routing) (ops.Operator, error) { diff --git a/go/vt/vtgate/planbuilder/operators/sequential.go b/go/vt/vtgate/planbuilder/operators/sequential.go new file mode 100644 index 00000000000..2b333c6270a --- /dev/null +++ b/go/vt/vtgate/planbuilder/operators/sequential.go @@ -0,0 +1,54 @@ +/* +Copyright 2023 The Vitess Authors. + +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 operators + +import ( + "vitess.io/vitess/go/vt/vtgate/planbuilder/operators/ops" + "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" +) + +type Sequential struct { + Sources []ops.Operator + + noPredicates + noColumns +} + +// Clone implements the Operator interface +func (s *Sequential) Clone(inputs []ops.Operator) ops.Operator { + newOp := *s + newOp.Sources = inputs + return &newOp +} + +func (s *Sequential) GetOrdering(*plancontext.PlanningContext) []ops.OrderBy { + return nil +} + +// Inputs implements the Operator interface +func (s *Sequential) Inputs() []ops.Operator { + return s.Sources +} + +// SetInputs implements the Operator interface +func (s *Sequential) SetInputs(ops []ops.Operator) { + s.Sources = ops +} + +func (s *Sequential) ShortDescription() string { + return "" +} diff --git a/go/vt/vtgate/planbuilder/plan_test.go b/go/vt/vtgate/planbuilder/plan_test.go index 472648828ef..9c1592fdf81 100644 --- a/go/vt/vtgate/planbuilder/plan_test.go +++ b/go/vt/vtgate/planbuilder/plan_test.go @@ -175,7 +175,23 @@ func setFks(t *testing.T, vschema *vindexes.VSchema) { _ = vschema.AddForeignKey("unsharded_fk_allow", "u_multicol_tbl2", createFkDefinition([]string{"cola", "colb"}, "u_multicol_tbl1", []string{"cola", "colb"}, sqlparser.SetNull, sqlparser.SetNull)) _ = vschema.AddForeignKey("unsharded_fk_allow", "u_multicol_tbl3", createFkDefinition([]string{"cola", "colb"}, "u_multicol_tbl2", []string{"cola", "colb"}, sqlparser.Cascade, sqlparser.Cascade)) - } + + _ = vschema.AddForeignKey("unsharded_fk_allow", "fk_t3", createFkDefinition([]string{"col"}, "fk_t2", []string{"col2"}, sqlparser.SetNull, sqlparser.SetNull)) + _ = vschema.AddForeignKey("unsharded_fk_allow", "fk_t4", createFkDefinition([]string{"col3"}, "fk_t3", []string{"col2"}, sqlparser.SetNull, sqlparser.SetNull)) + _ = vschema.AddPrimaryKey("unsharded_fk_allow", "fk_t2", []string{"id"}) + _ = vschema.AddPrimaryKey("unsharded_fk_allow", "fk_t3", []string{"id"}) + _ = vschema.AddPrimaryKey("unsharded_fk_allow", "fk_t4", []string{"id"}) + _ = vschema.AddUniqueKey("unsharded_fk_allow", "fk_t3", sqlparser.Exprs{sqlparser.NewColName("col")}) + } + + _ = vschema.AddPrimaryKey("unsharded_fk_allow", "u_tbl1", []string{"id"}) + _ = vschema.AddPrimaryKey("unsharded_fk_allow", "u_tbl9", []string{"id"}) + _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl9", sqlparser.Exprs{sqlparser.NewColName("col9")}) + _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl9", sqlparser.Exprs{&sqlparser.BinaryExpr{Operator: sqlparser.MultOp, Left: sqlparser.NewColName("col9"), Right: sqlparser.NewColName("foo")}}) + _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl9", sqlparser.Exprs{sqlparser.NewColName("col9"), sqlparser.NewColName("foo")}) + _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl9", sqlparser.Exprs{sqlparser.NewColName("foo"), sqlparser.NewColName("bar")}) + _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl9", sqlparser.Exprs{sqlparser.NewColName("bar"), sqlparser.NewColName("col9")}) + _ = vschema.AddUniqueKey("unsharded_fk_allow", "u_tbl8", sqlparser.Exprs{sqlparser.NewColName("col8")}) } func TestSystemTables57(t *testing.T) { diff --git a/go/vt/vtgate/planbuilder/sequential.go b/go/vt/vtgate/planbuilder/sequential.go new file mode 100644 index 00000000000..ff6abacb437 --- /dev/null +++ b/go/vt/vtgate/planbuilder/sequential.go @@ -0,0 +1,36 @@ +/* +Copyright 2023 The Vitess Authors. + +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 planbuilder + +import ( + "vitess.io/vitess/go/vt/vtgate/engine" +) + +type sequential struct { + sources []logicalPlan +} + +var _ logicalPlan = (*sequential)(nil) + +// Primitive implements the logicalPlan interface +func (s *sequential) Primitive() engine.Primitive { + var sources []engine.Primitive + for _, source := range s.sources { + sources = append(sources, source.Primitive()) + } + return engine.NewSequential(sources) +} diff --git a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json index afe42a45720..0a57806cb1d 100644 --- a/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/foreignkey_cases.json @@ -1690,9 +1690,113 @@ "plan": "VT12002: unsupported: cross-shard foreign keys" }, { - "comment": "replace with fk reference unsupported", + "comment": "replace into with table having primary key", "query": "replace into u_tbl1 (id, col1) values (1, 2)", - "plan": "VT12001: unsupported: REPLACE INTO with foreign keys" + "plan": { + "QueryType": "INSERT", + "Original": "replace into u_tbl1 (id, col1) values (1, 2)", + "Instructions": { + "OperatorType": "Sequential", + "Inputs": [ + { + "OperatorType": "FkCascade", + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select col1 from u_tbl1 where 1 != 1", + "Query": "select col1 from u_tbl1 where (id) in ((1)) for update", + "Table": "u_tbl1" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "FkCascade", + "BvName": "fkc_vals", + "Cols": [ + 0 + ], + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select col2 from u_tbl2 where 1 != 1", + "Query": "select col2 from u_tbl2 where (col2) in ::fkc_vals for update", + "Table": "u_tbl2" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "BvName": "fkc_vals1", + "Cols": [ + 0 + ], + "Query": "update u_tbl3 set col3 = null where (col3) in ::fkc_vals1", + "Table": "u_tbl3" + }, + { + "InputName": "Parent", + "OperatorType": "Delete", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "delete from u_tbl2 where (col2) in ::fkc_vals", + "Table": "u_tbl2" + } + ] + }, + { + "InputName": "Parent", + "OperatorType": "Delete", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "delete from u_tbl1 where (id) in ((1))", + "Table": "u_tbl1" + } + ] + }, + { + "OperatorType": "Insert", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "NoAutoCommit": true, + "Query": "insert into u_tbl1(id, col1) values (1, 2)", + "TableName": "u_tbl1" + } + ] + }, + "TablesUsed": [ + "unsharded_fk_allow.u_tbl1", + "unsharded_fk_allow.u_tbl2", + "unsharded_fk_allow.u_tbl3" + ] + } }, { "comment": "update on a multicol foreign key that set nulls and then cascades", @@ -2258,5 +2362,79 @@ "unsharded_fk_allow.u_multicol_tbl3" ] } + }, + { + "comment": "replace into with table having unique key and primary key", + "query": "replace into u_tbl9(id, col9) values (1, 10),(2, 20),(3, 30)", + "plan": { + "QueryType": "INSERT", + "Original": "replace into u_tbl9(id, col9) values (1, 10),(2, 20),(3, 30)", + "Instructions": { + "OperatorType": "Sequential", + "Inputs": [ + { + "OperatorType": "FkCascade", + "Inputs": [ + { + "InputName": "Selection", + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "FieldQuery": "select col9 from u_tbl9 where 1 != 1", + "Query": "select col9 from u_tbl9 where (col9) in ((10), (20), (30)) or (col9 * foo) in ((10 * null), (20 * null), (30 * null)) or (bar, col9) in ((1, 10), (1, 20), (1, 30)) or (id) in ((1), (2), (3)) for update", + "Table": "u_tbl9" + }, + { + "InputName": "CascadeChild-1", + "OperatorType": "Update", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "BvName": "fkc_vals", + "Cols": [ + 0 + ], + "Query": "update u_tbl8 set col8 = null where (col8) in ::fkc_vals", + "Table": "u_tbl8" + }, + { + "InputName": "Parent", + "OperatorType": "Delete", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "Query": "delete from u_tbl9 where (col9) in ((10), (20), (30)) or (col9 * foo) in ((10 * null), (20 * null), (30 * null)) or (bar, col9) in ((1, 10), (1, 20), (1, 30)) or (id) in ((1), (2), (3))", + "Table": "u_tbl9" + } + ] + }, + { + "OperatorType": "Insert", + "Variant": "Unsharded", + "Keyspace": { + "Name": "unsharded_fk_allow", + "Sharded": false + }, + "TargetTabletType": "PRIMARY", + "NoAutoCommit": true, + "Query": "insert into u_tbl9(id, col9) values (1, 10), (2, 20), (3, 30)", + "TableName": "u_tbl9" + } + ] + }, + "TablesUsed": [ + "unsharded_fk_allow.u_tbl8", + "unsharded_fk_allow.u_tbl9" + ] + } } ] diff --git a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json index a7824126c98..d5b9ece501b 100644 --- a/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json +++ b/go/vt/vtgate/planbuilder/testdata/vschemas/schema.json @@ -760,7 +760,12 @@ "u_tbl6": {}, "u_tbl7": {}, "u_tbl8": {}, - "u_tbl9": {}, + "u_tbl9": { + "columns": [ + {"name": "foo"}, + {"name": "bar", "default": "1"} + ] + }, "u_tbl": {}, "u_multicol_tbl1": {}, "u_multicol_tbl2": {}, diff --git a/go/vt/vtgate/schema/tracker.go b/go/vt/vtgate/schema/tracker.go index 369ab178986..c4b48ce8156 100644 --- a/go/vt/vtgate/schema/tracker.go +++ b/go/vt/vtgate/schema/tracker.go @@ -216,6 +216,15 @@ func (t *Tracker) GetForeignKeys(ks string, tbl string) []*sqlparser.ForeignKeyD return tblInfo.ForeignKeys } +// GetIndexes returns the indexes for table in the given keyspace. +func (t *Tracker) GetIndexes(ks string, tbl string) []*sqlparser.IndexDefinition { + t.mu.Lock() + defer t.mu.Unlock() + + tblInfo := t.tables.get(ks, tbl) + return tblInfo.Indexes +} + // Tables returns a map with the columns for all known tables in the keyspace func (t *Tracker) Tables(ks string) map[string]*vindexes.TableInfo { t.mu.Lock() @@ -293,7 +302,7 @@ func (t *Tracker) updateTables(keyspace string, res map[string]string) { cols := getColumns(ddl.TableSpec) fks := getForeignKeys(ddl.TableSpec) - t.tables.set(keyspace, tableName, cols, fks) + t.tables.set(keyspace, tableName, cols, fks, ddl.TableSpec.Indexes) } } @@ -308,6 +317,7 @@ func getColumns(tblSpec *sqlparser.TableSpec) []vindexes.Column { Type: column.Type.SQLType(), CollationName: colCollation, Invisible: column.Type.Invisible(), + Default: column.Type.Options.Default, }) } return cols @@ -403,13 +413,13 @@ type tableMap struct { m map[keyspaceStr]map[tableNameStr]*vindexes.TableInfo } -func (tm *tableMap) set(ks, tbl string, cols []vindexes.Column, fks []*sqlparser.ForeignKeyDefinition) { +func (tm *tableMap) set(ks, tbl string, cols []vindexes.Column, fks []*sqlparser.ForeignKeyDefinition, indexes []*sqlparser.IndexDefinition) { m := tm.m[ks] if m == nil { m = make(map[tableNameStr]*vindexes.TableInfo) tm.m[ks] = m } - m[tbl] = &vindexes.TableInfo{Columns: cols, ForeignKeys: fks} + m[tbl] = &vindexes.TableInfo{Columns: cols, ForeignKeys: fks, Indexes: indexes} } func (tm *tableMap) get(ks, tbl string) *vindexes.TableInfo { diff --git a/go/vt/vtgate/schema/tracker_test.go b/go/vt/vtgate/schema/tracker_test.go index 4f514fec101..88b9dfa0d50 100644 --- a/go/vt/vtgate/schema/tracker_test.go +++ b/go/vt/vtgate/schema/tracker_test.go @@ -264,8 +264,8 @@ func TestViewsTracking(t *testing.T) { testTracker(t, schemaDefResult, testcases) } -// TestTableInfoRetrieval tests that the tracker is able to retrieve required information from ddl statement. -func TestTableInfoRetrieval(t *testing.T) { +// TestFKInfoRetrieval tests that the tracker is able to retrieve required foreign key information from ddl statement. +func TestFKInfoRetrieval(t *testing.T) { schemaDefResult := []map[string]string{{ "my_tbl": "CREATE TABLE `my_tbl` (" + "`id` bigint NOT NULL AUTO_INCREMENT," + @@ -293,8 +293,8 @@ func TestTableInfoRetrieval(t *testing.T) { expTbl: map[string][]vindexes.Column{ "my_tbl": { {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT64, CollationName: "utf8mb4_0900_ai_ci"}, - {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci"}, - {Name: sqlparser.NewIdentifierCI("email"), Type: querypb.Type_VARBINARY, CollationName: "utf8mb4_0900_ai_ci"}, + {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci", Default: &sqlparser.NullVal{}}, + {Name: sqlparser.NewIdentifierCI("email"), Type: querypb.Type_VARBINARY, CollationName: "utf8mb4_0900_ai_ci", Default: &sqlparser.NullVal{}}, }, }, }, { @@ -303,14 +303,14 @@ func TestTableInfoRetrieval(t *testing.T) { expTbl: map[string][]vindexes.Column{ "my_tbl": { {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT64, CollationName: "utf8mb4_0900_ai_ci"}, - {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci"}, - {Name: sqlparser.NewIdentifierCI("email"), Type: querypb.Type_VARBINARY, CollationName: "utf8mb4_0900_ai_ci"}, + {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci", Default: &sqlparser.NullVal{}}, + {Name: sqlparser.NewIdentifierCI("email"), Type: querypb.Type_VARBINARY, CollationName: "utf8mb4_0900_ai_ci", Default: &sqlparser.NullVal{}}, }, "my_child_tbl": { {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT64, CollationName: "utf8mb4_0900_ai_ci"}, - {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci"}, - {Name: sqlparser.NewIdentifierCI("code"), Type: querypb.Type_VARCHAR, CollationName: "utf8mb4_0900_ai_ci"}, - {Name: sqlparser.NewIdentifierCI("my_id"), Type: querypb.Type_INT64, CollationName: "utf8mb4_0900_ai_ci"}, + {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci", Default: &sqlparser.NullVal{}}, + {Name: sqlparser.NewIdentifierCI("code"), Type: querypb.Type_VARCHAR, CollationName: "utf8mb4_0900_ai_ci", Default: &sqlparser.NullVal{}}, + {Name: sqlparser.NewIdentifierCI("my_id"), Type: querypb.Type_INT64, CollationName: "utf8mb4_0900_ai_ci", Default: &sqlparser.NullVal{}}, }, }, expFk: map[string]string{ @@ -322,12 +322,73 @@ func TestTableInfoRetrieval(t *testing.T) { testTracker(t, schemaDefResult, testcases) } +// TestIndexInfoRetrieval tests that the tracker is able to retrieve required index information from ddl statement. +func TestIndexInfoRetrieval(t *testing.T) { + schemaDefResult := []map[string]string{{ + "my_tbl": "CREATE TABLE `my_tbl` (" + + "`id` bigint NOT NULL AUTO_INCREMENT," + + "`name` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL," + + "`email` varbinary(100) DEFAULT NULL," + + "PRIMARY KEY (`id`)," + + "KEY `id` (`id`,`name`)) " + + "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", + }, { + // initial load of view - kept empty + }, { + "my_tbl": "CREATE TABLE `my_tbl` (" + + "`id` bigint NOT NULL AUTO_INCREMENT," + + "`name` varchar(50) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL," + + "`email` varbinary(100) DEFAULT NULL," + + "PRIMARY KEY (`id`)," + + "KEY `id` (`id`,`name`), " + + "UNIQUE KEY `email` (`email`)) " + + "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci", + }} + + testcases := []testCases{{ + testName: "initial table load", + expTbl: map[string][]vindexes.Column{ + "my_tbl": { + {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT64, CollationName: "utf8mb4_0900_ai_ci"}, + {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci", Default: &sqlparser.NullVal{}}, + {Name: sqlparser.NewIdentifierCI("email"), Type: querypb.Type_VARBINARY, CollationName: "utf8mb4_0900_ai_ci", Default: &sqlparser.NullVal{}}, + }, + }, + expIdx: map[string][]string{ + "my_tbl": { + "primary key (id)", + "key id (id, `name`)", + }, + }, + }, { + testName: "next load", + updTbl: []string{"my_tbl"}, + expTbl: map[string][]vindexes.Column{ + "my_tbl": { + {Name: sqlparser.NewIdentifierCI("id"), Type: querypb.Type_INT64, CollationName: "utf8mb4_0900_ai_ci"}, + {Name: sqlparser.NewIdentifierCI("name"), Type: querypb.Type_VARCHAR, CollationName: "latin1_swedish_ci", Default: &sqlparser.NullVal{}}, + {Name: sqlparser.NewIdentifierCI("email"), Type: querypb.Type_VARBINARY, CollationName: "utf8mb4_0900_ai_ci", Default: &sqlparser.NullVal{}}, + }, + }, + expIdx: map[string][]string{ + "my_tbl": { + "primary key (id)", + "key id (id, `name`)", + "unique key email (email)", + }, + }, + }} + + testTracker(t, schemaDefResult, testcases) +} + type testCases struct { testName string updTbl []string expTbl map[string][]vindexes.Column expFk map[string]string + expIdx map[string][]string updView []string expView map[string]string @@ -376,6 +437,16 @@ func testTracker(t *testing.T, schemaDefResult []map[string]string, tcases []tes utils.MustMatch(t, tcase.expFk[k], sqlparser.String(fk), "mismatch foreign keys for table: ", k) } } + expIndexes := tcase.expIdx[k] + if len(expIndexes) > 0 { + idxs := tracker.GetIndexes(keyspace, k) + if len(expIndexes) != len(idxs) { + t.Fatalf("mismatch index for table: %s", k) + } + for i, idx := range idxs { + utils.MustMatch(t, expIndexes[i], sqlparser.String(idx), "mismatch index for table: ", k) + } + } } for k, v := range tcase.expView { diff --git a/go/vt/vtgate/vindexes/foreign_keys.go b/go/vt/vtgate/vindexes/foreign_keys.go index 74f9ce74844..275a0674998 100644 --- a/go/vt/vtgate/vindexes/foreign_keys.go +++ b/go/vt/vtgate/vindexes/foreign_keys.go @@ -144,3 +144,33 @@ func (vschema *VSchema) AddForeignKey(ksname, childTableName string, fkConstrain cTbl.ParentForeignKeys = append(cTbl.ParentForeignKeys, NewParentFkInfo(pTbl, fkConstraint)) return nil } + +// AddPrimaryKey is for testing only. +func (vschema *VSchema) AddPrimaryKey(ksname, tblName string, cols []string) error { + ks, ok := vschema.Keyspaces[ksname] + if !ok { + return fmt.Errorf("keyspace %s not found in vschema", ksname) + } + tbl, ok := ks.Tables[tblName] + if !ok { + return fmt.Errorf("table %s not found in keyspace %s", tblName, ksname) + } + for _, col := range cols { + tbl.PrimaryKey = append(tbl.PrimaryKey, sqlparser.NewIdentifierCI(col)) + } + return nil +} + +// AddUniqueKey is for testing only. +func (vschema *VSchema) AddUniqueKey(ksname, tblName string, exprs sqlparser.Exprs) error { + ks, ok := vschema.Keyspaces[ksname] + if !ok { + return fmt.Errorf("keyspace %s not found in vschema", ksname) + } + tbl, ok := ks.Tables[tblName] + if !ok { + return fmt.Errorf("table %s not found in keyspace %s", tblName, ksname) + } + tbl.UniqueKeys = append(tbl.UniqueKeys, exprs) + return nil +} diff --git a/go/vt/vtgate/vindexes/vschema.go b/go/vt/vtgate/vindexes/vschema.go index 4e9f527eb83..66321b7a41c 100644 --- a/go/vt/vtgate/vindexes/vschema.go +++ b/go/vt/vtgate/vindexes/vschema.go @@ -118,6 +118,12 @@ type Table struct { ChildForeignKeys []ChildFKInfo `json:"child_foreign_keys,omitempty"` ParentForeignKeys []ParentFKInfo `json:"parent_foreign_keys,omitempty"` + + // index can be columns or expression. + // For Primary key, functional indexes are not allowed, therefore it will only be columns. + // MySQL error message: ERROR 3756 (HY000): The primary key cannot be a functional index + PrimaryKey sqlparser.Columns `json:"primary_key,omitempty"` + UniqueKeys []sqlparser.Exprs `json:"unique_keys,omitempty"` } // GetTableName gets the sqlparser.TableName for the vindex Table. @@ -148,6 +154,7 @@ type ColumnVindex struct { type TableInfo struct { Columns []Column ForeignKeys []*sqlparser.ForeignKeyDefinition + Indexes []*sqlparser.IndexDefinition } // IsUnique is used to tell whether the ColumnVindex @@ -178,6 +185,7 @@ type Column struct { Name sqlparser.IdentifierCI `json:"name"` Type querypb.Type `json:"type"` CollationName string `json:"collation_name"` + Default sqlparser.Expr `json:"default,omitempty"` // Invisible marks this as a column that will not be automatically included in `*` projections Invisible bool `json:"invisible"` @@ -185,13 +193,22 @@ type Column struct { // MarshalJSON returns a JSON representation of Column. func (col *Column) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Name string `json:"name"` - Type string `json:"type,omitempty"` + cj := struct { + Name string `json:"name"` + Type string `json:"type,omitempty"` + Invisible bool `json:"invisible,omitempty"` + Default string `json:"default,omitempty"` }{ Name: col.Name.String(), Type: querypb.Type_name[int32(col.Type)], - }) + } + if col.Invisible { + cj.Invisible = true + } + if col.Default != nil { + cj.Default = sqlparser.String(col.Default) + } + return json.Marshal(cj) } // KeyspaceSchema contains the schema(table) for a keyspace. @@ -612,8 +629,17 @@ func buildTables(ks *vschemapb.Keyspace, vschema *VSchema, ksvschema *KeyspaceSc tname, ) } + var colDefault sqlparser.Expr + if col.Default != "" { + var err error + colDefault, err = sqlparser.ParseExpr(col.Default) + if err != nil { + return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, + "could not parse the '%s' column's default expression '%s' for table '%s'", col.Name, col.Default, tname) + } + } colNames[name.Lowered()] = true - t.Columns = append(t.Columns, Column{Name: name, Type: col.Type, Invisible: col.Invisible}) + t.Columns = append(t.Columns, Column{Name: name, Type: col.Type, Invisible: col.Invisible, Default: colDefault}) } // Initialize ColumnVindexes. diff --git a/go/vt/vtgate/vindexes/vschema_test.go b/go/vt/vtgate/vindexes/vschema_test.go index a59ec78139d..92c95d64674 100644 --- a/go/vt/vtgate/vindexes/vschema_test.go +++ b/go/vt/vtgate/vindexes/vschema_test.go @@ -315,10 +315,12 @@ func TestVSchemaColumns(t *testing.T) { "unsharded": { Tables: map[string]*vschemapb.Table{ "t1": { - Columns: []*vschemapb.Column{{ - Name: "c1"}, { - Name: "c2", - Type: sqltypes.VarChar}}}}}}} + Columns: []*vschemapb.Column{ + {Name: "c1"}, + {Name: "c2", Type: sqltypes.VarChar}, + {Name: "c3", Type: sqltypes.VarChar, Default: "''"}, + {Name: "c4", Type: sqltypes.TypeJSON, Default: "json_array()"}, + }}}}}} got := BuildVSchema(&good) require.NoError(t, got.Keyspaces["unsharded"].Error) @@ -327,6 +329,8 @@ func TestVSchemaColumns(t *testing.T) { require.NoError(t, err) assertColumn(t, t1.Columns[0], "c1", sqltypes.Null) assertColumn(t, t1.Columns[1], "c2", sqltypes.VarChar) + assertColumnWithDefault(t, t1.Columns[2], "c3", sqltypes.VarChar, sqlparser.NewStrLiteral("")) + assertColumnWithDefault(t, t1.Columns[3], "c4", sqltypes.TypeJSON, &sqlparser.JSONArrayExpr{}) } func TestVSchemaViews(t *testing.T) { @@ -2687,8 +2691,9 @@ func TestVSchemaJSON(t *testing.T) { Columns: []Column{{ Name: sqlparser.NewIdentifierCI("c1"), }, { - Name: sqlparser.NewIdentifierCI("c2"), - Type: sqltypes.VarChar, + Name: sqlparser.NewIdentifierCI("c2"), + Type: sqltypes.VarChar, + Invisible: true, }}, }, "t2": { @@ -2761,7 +2766,8 @@ func TestVSchemaJSON(t *testing.T) { }, { "name": "c2", - "type": "VARCHAR" + "type": "VARCHAR", + "invisible": true } ] }, @@ -3165,5 +3171,11 @@ func assertVindexMatches(t *testing.T, cv *ColumnVindex, v Vindex, name string, func assertColumn(t *testing.T, col Column, expectedName string, expectedType querypb.Type) { assert.True(t, col.Name.EqualString(expectedName), "column name does not match") assert.Equal(t, expectedType, col.Type, "column type does not match") +} +func assertColumnWithDefault(t *testing.T, col Column, expectedName string, expectedType querypb.Type, expDefault sqlparser.Expr) { + assertColumn(t, col, expectedName, expectedType) + if expDefault != nil { + assert.Equal(t, expDefault, col.Default, "column default does not match") + } } diff --git a/go/vt/vtgate/vschema_manager.go b/go/vt/vtgate/vschema_manager.go index 7f2b7267dc0..20c11634b54 100644 --- a/go/vt/vtgate/vschema_manager.go +++ b/go/vt/vtgate/vschema_manager.go @@ -210,19 +210,37 @@ func (vm *VSchemaManager) updateFromSchema(vschema *vindexes.VSchema) { // Now that we have ensured that all the tables are created, we can start populating the foreign keys // in the tables. for tblName, tblInfo := range m { + rTbl, err := vschema.FindRoutedTable(ksName, tblName, topodatapb.TabletType_PRIMARY) + if err != nil { + log.Errorf("error finding routed table %s: %v", tblName, err) + continue + } for _, fkDef := range tblInfo.ForeignKeys { parentTbl, err := vschema.FindRoutedTable(ksName, fkDef.ReferenceDefinition.ReferencedTable.Name.String(), topodatapb.TabletType_PRIMARY) if err != nil { log.Errorf("error finding parent table %s: %v", fkDef.ReferenceDefinition.ReferencedTable.Name.String(), err) continue } - childTbl, err := vschema.FindRoutedTable(ksName, tblName, topodatapb.TabletType_PRIMARY) - if err != nil { - log.Errorf("error finding child table %s: %v", tblName, err) - continue + rTbl.ParentForeignKeys = append(rTbl.ParentForeignKeys, vindexes.NewParentFkInfo(parentTbl, fkDef)) + parentTbl.ChildForeignKeys = append(parentTbl.ChildForeignKeys, vindexes.NewChildFkInfo(rTbl, fkDef)) + } + for _, idxDef := range tblInfo.Indexes { + switch idxDef.Info.Type { + case sqlparser.IndexTypePrimary: + for _, idxCol := range idxDef.Columns { + rTbl.PrimaryKey = append(rTbl.PrimaryKey, idxCol.Column) + } + case sqlparser.IndexTypeUnique: + var uniqueKey sqlparser.Exprs + for _, idxCol := range idxDef.Columns { + if idxCol.Expression == nil { + uniqueKey = append(uniqueKey, sqlparser.NewColName(idxCol.Column.String())) + } else { + uniqueKey = append(uniqueKey, idxCol.Expression) + } + } + rTbl.UniqueKeys = append(rTbl.UniqueKeys, uniqueKey) } - childTbl.ParentForeignKeys = append(childTbl.ParentForeignKeys, vindexes.NewParentFkInfo(parentTbl, fkDef)) - parentTbl.ChildForeignKeys = append(parentTbl.ChildForeignKeys, vindexes.NewChildFkInfo(childTbl, fkDef)) } } diff --git a/go/vt/vtgate/vschema_manager_test.go b/go/vt/vtgate/vschema_manager_test.go index 9c51266c26a..38e22fee0a2 100644 --- a/go/vt/vtgate/vschema_manager_test.go +++ b/go/vt/vtgate/vschema_manager_test.go @@ -82,6 +82,27 @@ func TestVSchemaUpdate(t *testing.T) { ParentColumns: sqlparserCols1, }) + idxTbl1 := &vindexes.Table{ + Name: sqlparser.NewIdentifierCS("idxTbl1"), + Keyspace: ks, + ColumnListAuthoritative: true, + PrimaryKey: sqlparser.Columns{sqlparser.NewIdentifierCI("a")}, + UniqueKeys: []sqlparser.Exprs{ + {sqlparser.NewColName("b")}, + {sqlparser.NewColName("c"), sqlparser.NewColName("d")}, + }, + } + idxTbl2 := &vindexes.Table{ + Name: sqlparser.NewIdentifierCS("idxTbl2"), + Keyspace: ks, + ColumnListAuthoritative: true, + PrimaryKey: sqlparser.Columns{sqlparser.NewIdentifierCI("a")}, + UniqueKeys: []sqlparser.Exprs{ + {&sqlparser.BinaryExpr{Operator: sqlparser.DivOp, Left: sqlparser.NewColName("b"), Right: sqlparser.NewIntLiteral("2")}}, + {sqlparser.NewColName("c"), &sqlparser.BinaryExpr{Operator: sqlparser.PlusOp, Left: sqlparser.NewColName("d"), Right: sqlparser.NewColName("e")}}, + }, + } + tcases := []struct { name string srvVschema *vschemapb.SrvVSchema @@ -192,41 +213,18 @@ func TestVSchemaUpdate(t *testing.T) { Sharded: false, ForeignKeyMode: vschemapb.Keyspace_managed, Tables: map[string]*vschemapb.Table{ - "t1": { - Columns: []*vschemapb.Column{ - { - Name: "id", - Type: querypb.Type_INT64, - }, - }, - }, - "t2": { - Columns: []*vschemapb.Column{ - { - Name: "id", - Type: querypb.Type_INT64, - }, - }, - }, + "t1": {Columns: []*vschemapb.Column{{Name: "id", Type: querypb.Type_INT64}}}, + "t2": {Columns: []*vschemapb.Column{{Name: "id", Type: querypb.Type_INT64}}}, "multicol_t1": { Columns: []*vschemapb.Column{ - { - Name: "uid", - Type: querypb.Type_INT64, - }, { - Name: "name", - Type: querypb.Type_VARCHAR, - }, + {Name: "uid", Type: querypb.Type_INT64}, + {Name: "name", Type: querypb.Type_VARCHAR}, }, - }, "multicol_t2": { + }, + "multicol_t2": { Columns: []*vschemapb.Column{ - { - Name: "uid", - Type: querypb.Type_INT64, - }, { - Name: "name", - Type: querypb.Type_VARCHAR, - }, + {Name: "uid", Type: querypb.Type_INT64}, + {Name: "name", Type: querypb.Type_VARCHAR}, }, }, }, @@ -249,6 +247,69 @@ func TestVSchemaUpdate(t *testing.T) { }, }, }, + }, { + name: "indexes in schema using columns", + currentVSchema: &vindexes.VSchema{}, + schema: map[string]*vindexes.TableInfo{ + "idxTbl1": { + Indexes: []*sqlparser.IndexDefinition{{ + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypePrimary}, + Columns: []*sqlparser.IndexColumn{ + {Column: sqlparser.NewIdentifierCI("a")}, + }, + }, { + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypeUnique}, + Columns: []*sqlparser.IndexColumn{ + {Column: sqlparser.NewIdentifierCI("b")}, + }, + }, { + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypeDefault}, + Columns: []*sqlparser.IndexColumn{ + {Column: sqlparser.NewIdentifierCI("x")}, + {Column: sqlparser.NewIdentifierCI("y")}, + }, + }, { + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypeUnique}, + Columns: []*sqlparser.IndexColumn{ + {Column: sqlparser.NewIdentifierCI("c")}, + {Column: sqlparser.NewIdentifierCI("d")}, + }, + }}, + }, + }, + srvVschema: makeTestSrvVSchema("ks", false, nil), + expected: makeTestVSchema("ks", false, map[string]*vindexes.Table{"idxTbl1": idxTbl1}), + }, { + name: "indexes in schema using expressions", + currentVSchema: &vindexes.VSchema{}, + schema: map[string]*vindexes.TableInfo{ + "idxTbl2": { + Indexes: []*sqlparser.IndexDefinition{{ + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypePrimary}, + Columns: []*sqlparser.IndexColumn{ + {Column: sqlparser.NewIdentifierCI("a")}, + }, + }, { + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypeUnique}, + Columns: []*sqlparser.IndexColumn{ + {Expression: &sqlparser.BinaryExpr{Operator: sqlparser.DivOp, Left: sqlparser.NewColName("b"), Right: sqlparser.NewIntLiteral("2")}}, + }, + }, { + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypeDefault}, + Columns: []*sqlparser.IndexColumn{ + {Expression: &sqlparser.BinaryExpr{Operator: sqlparser.PlusOp, Left: sqlparser.NewColName("x"), Right: sqlparser.NewColName("y")}}, + }, + }, { + Info: &sqlparser.IndexInfo{Type: sqlparser.IndexTypeUnique}, + Columns: []*sqlparser.IndexColumn{ + {Column: sqlparser.NewIdentifierCI("c")}, + {Expression: &sqlparser.BinaryExpr{Operator: sqlparser.PlusOp, Left: sqlparser.NewColName("d"), Right: sqlparser.NewColName("e")}}, + }, + }}, + }, + }, + srvVschema: makeTestSrvVSchema("ks", false, nil), + expected: makeTestVSchema("ks", false, map[string]*vindexes.Table{"idxTbl2": idxTbl2}), }} vm := &VSchemaManager{} diff --git a/proto/vschema.proto b/proto/vschema.proto index 067be686db5..c6665688c23 100644 --- a/proto/vschema.proto +++ b/proto/vschema.proto @@ -127,6 +127,7 @@ message Column { string name = 1; query.Type type = 2; bool invisible = 3; + string default = 4; } // SrvVSchema is the roll-up of all the Keyspace schema for a cell. diff --git a/web/vtadmin/src/proto/vtadmin.d.ts b/web/vtadmin/src/proto/vtadmin.d.ts index 398d93080dc..bda230c6570 100644 --- a/web/vtadmin/src/proto/vtadmin.d.ts +++ b/web/vtadmin/src/proto/vtadmin.d.ts @@ -41592,6 +41592,9 @@ export namespace vschema { /** Column invisible */ invisible?: (boolean|null); + + /** Column default */ + "default"?: (string|null); } /** Represents a Column. */ @@ -41612,6 +41615,9 @@ export namespace vschema { /** Column invisible. */ public invisible: boolean; + /** Column default. */ + public default: string; + /** * Creates a new Column instance using the specified properties. * @param [properties] Properties to set diff --git a/web/vtadmin/src/proto/vtadmin.js b/web/vtadmin/src/proto/vtadmin.js index 9ddce1d0059..dc1679ee3c0 100644 --- a/web/vtadmin/src/proto/vtadmin.js +++ b/web/vtadmin/src/proto/vtadmin.js @@ -101273,6 +101273,7 @@ export const vschema = $root.vschema = (() => { * @property {string|null} [name] Column name * @property {query.Type|null} [type] Column type * @property {boolean|null} [invisible] Column invisible + * @property {string|null} ["default"] Column default */ /** @@ -101314,6 +101315,14 @@ export const vschema = $root.vschema = (() => { */ Column.prototype.invisible = false; + /** + * Column default. + * @member {string} default + * @memberof vschema.Column + * @instance + */ + Column.prototype["default"] = ""; + /** * Creates a new Column instance using the specified properties. * @function create @@ -101344,6 +101353,8 @@ export const vschema = $root.vschema = (() => { writer.uint32(/* id 2, wireType 0 =*/16).int32(message.type); if (message.invisible != null && Object.hasOwnProperty.call(message, "invisible")) writer.uint32(/* id 3, wireType 0 =*/24).bool(message.invisible); + if (message["default"] != null && Object.hasOwnProperty.call(message, "default")) + writer.uint32(/* id 4, wireType 2 =*/34).string(message["default"]); return writer; }; @@ -101390,6 +101401,10 @@ export const vschema = $root.vschema = (() => { message.invisible = reader.bool(); break; } + case 4: { + message["default"] = reader.string(); + break; + } default: reader.skipType(tag & 7); break; @@ -101472,6 +101487,9 @@ export const vschema = $root.vschema = (() => { if (message.invisible != null && message.hasOwnProperty("invisible")) if (typeof message.invisible !== "boolean") return "invisible: boolean expected"; + if (message["default"] != null && message.hasOwnProperty("default")) + if (!$util.isString(message["default"])) + return "default: string expected"; return null; }; @@ -101639,6 +101657,8 @@ export const vschema = $root.vschema = (() => { } if (object.invisible != null) message.invisible = Boolean(object.invisible); + if (object["default"] != null) + message["default"] = String(object["default"]); return message; }; @@ -101659,6 +101679,7 @@ export const vschema = $root.vschema = (() => { object.name = ""; object.type = options.enums === String ? "NULL_TYPE" : 0; object.invisible = false; + object["default"] = ""; } if (message.name != null && message.hasOwnProperty("name")) object.name = message.name; @@ -101666,6 +101687,8 @@ export const vschema = $root.vschema = (() => { object.type = options.enums === String ? $root.query.Type[message.type] === undefined ? message.type : $root.query.Type[message.type] : message.type; if (message.invisible != null && message.hasOwnProperty("invisible")) object.invisible = message.invisible; + if (message["default"] != null && message.hasOwnProperty("default")) + object["default"] = message["default"]; return object; };