diff --git a/conf/config.yaml b/conf/config.yaml index f5b016fe..795bf7ad 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -94,7 +94,7 @@ data: port: 3306 username: root password: "123456" - database: employees_show + database: employees_shadow weight: r10w10 sharding_rule: tables: @@ -120,7 +120,7 @@ data: shadow_rule: tables: - - name: student + - name: employees.student enable: false group_node: employees_shadow match_rules: @@ -137,4 +137,4 @@ data: - operation: [select] match_type: hint attributes: - - shadow: true + - value: "shadow" diff --git a/integration_test/config/db_tbl/config.yaml b/integration_test/config/db_tbl/config.yaml index b3d9702a..8001aa7f 100644 --- a/integration_test/config/db_tbl/config.yaml +++ b/integration_test/config/db_tbl/config.yaml @@ -84,6 +84,15 @@ data: password: "123456" database: employees_0003 weight: r10w10 + - name: employees_shadow + nodes: + - name: node_shadow + host: arana-mysql + port: 3306 + username: root + password: "123456" + database: employees_shadow + weight: r10w10 sharding_rule: tables: @@ -105,3 +114,13 @@ data: tbl_pattern: student_${0000..0031} attributes: sqlMaxLimit: -1 + shadow_rule: + tables: + - name: employees.student + enable: true + group_node: employees_shadow + match_rules: + - operation: [select,insert,update,delete] + match_type: hint + attributes: + - value: "shadow" diff --git a/integration_test/config/shadow/config.yaml b/integration_test/config/shadow/config.yaml new file mode 100644 index 00000000..40a4afdd --- /dev/null +++ b/integration_test/config/shadow/config.yaml @@ -0,0 +1,129 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +kind: ConfigMap +apiVersion: "1.0" +metadata: + name: arana-config +data: + listeners: + - protocol_type: mysql + server_version: 5.7.0 + socket_address: + address: 0.0.0.0 + port: 13306 + + tenants: + - name: arana + users: + - username: root + password: "123456" + - username: arana + password: "123456" + + clusters: + - name: employees + type: mysql + sql_max_limit: -1 + tenant: arana + parameters: + max_allowed_packet: 256M + groups: + - name: employees_0000 + nodes: + - name: node0 + host: arana-mysql + port: 3306 + username: root + password: "123456" + database: employees_0000 + weight: r10w10 + parameters: + - name: node0_r_0 + host: arana-mysql + port: 3306 + username: root + password: "123456" + database: employees_0000_r + weight: r0w0 + - name: employees_0001 + nodes: + - name: node1 + host: arana-mysql + port: 3306 + username: root + password: "123456" + database: employees_0001 + weight: r10w10 + - name: employees_0002 + nodes: + - name: node2 + host: arana-mysql + port: 3306 + username: root + password: "123456" + database: employees_0002 + weight: r10w10 + - name: employees_0003 + nodes: + - name: node3 + host: arana-mysql + port: 3306 + username: root + password: "123456" + database: employees_0003 + weight: r10w10 + - name: employees_shadow + nodes: + - name: node_shadow + host: arana-mysql + port: 3306 + username: root + password: "123456" + database: employees_shadow + weight: r10w10 + sharding_rule: + tables: + - name: employees.student + allow_full_scan: true + sequence: + type: snowflake + option: + db_rules: + - column: uid + type: scriptExpr + expr: parseInt($value % 32 / 8) + tbl_rules: + - column: uid + type: scriptExpr + expr: $value % 32 + step: 32 + topology: + db_pattern: employees_${0000..0003} + tbl_pattern: student_${0000..0031} + attributes: + sqlMaxLimit: -1 + shadow_rule: + tables: + - name: employees.student + enable: true + group_node: employees_shadow + match_rules: + - operation: [select] + match_type: hint + attributes: + - value: "shadow" diff --git a/integration_test/config/shadow/data.yaml b/integration_test/config/shadow/data.yaml new file mode 100644 index 00000000..112cb3c1 --- /dev/null +++ b/integration_test/config/shadow/data.yaml @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +kind: DataSet +metadata: + tables: + - name: "order" + columns: + - name: "name" + type: "string" + - name: "value" + type: "string" +data: + - name: "order" + value: + - ["test", "test1"] diff --git a/integration_test/config/shadow/expected.yaml b/integration_test/config/shadow/expected.yaml new file mode 100644 index 00000000..542d3782 --- /dev/null +++ b/integration_test/config/shadow/expected.yaml @@ -0,0 +1,30 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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. +# + +kind: excepted +metadata: + tables: + - name: "sequence" + columns: + - name: "name" + type: "string" + - name: "value" + type: "string" +data: + - name: "sequence" + value: + - ["1", "2"] diff --git a/integration_test/scene/shadow/integration_test.go b/integration_test/scene/shadow/integration_test.go new file mode 100644 index 00000000..a17c7472 --- /dev/null +++ b/integration_test/scene/shadow/integration_test.go @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 test + +import ( + "strings" + "testing" +) + +import ( + _ "github.com/go-sql-driver/mysql" // register mysql + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +import ( + "github.com/arana-db/arana/test" +) + +type IntegrationSuite struct { + *test.MySuite +} + +func TestSuite(t *testing.T) { + su := test.NewMySuite( + test.WithMySQLServerAuth("root", "123456"), + test.WithMySQLDatabase("employees"), + test.WithConfig("../integration_test/config/shadow/config.yaml"), + test.WithScriptPath("../integration_test/scripts/shadow"), + test.WithTestCasePath("../../testcase/casetest.yaml"), + // WithDevMode(), // NOTICE: UNCOMMENT IF YOU WANT TO DEBUG LOCAL ARANA SERVER!!! + ) + suite.Run(t, &IntegrationSuite{su}) +} + +func (s *IntegrationSuite) TestShadowScene() { + var ( + db = s.DB() + t = s.T() + ) + tx, err := db.Begin() + assert.NoError(t, err, "should begin a new tx") + + cases := s.TestCases() + for _, sqlCase := range cases.ExecCases { + for _, sense := range sqlCase.Sense { + if strings.Compare(strings.TrimSpace(sense), "shadow") == 1 { + params := strings.Split(sqlCase.Parameters, ",") + args := make([]interface{}, 0, len(params)) + for _, param := range params { + k, _ := test.GetValueByType(param) + args = append(args, k) + } + + // Execute sql + result, err := tx.Exec(sqlCase.SQL, args...) + assert.NoError(t, err, "exec not right") + err = sqlCase.ExpectedResult.CompareRow(result) + assert.NoError(t, err, err) + } + } + } + + for _, sqlCase := range cases.QueryRowCases { + for _, sense := range sqlCase.Sense { + if strings.Compare(strings.TrimSpace(sense), "shadow") == 1 { + params := strings.Split(sqlCase.Parameters, ",") + args := make([]interface{}, 0, len(params)) + for _, param := range params { + k, _ := test.GetValueByType(param) + args = append(args, k) + } + + result := tx.QueryRow(sqlCase.SQL, args...) + err = sqlCase.ExpectedResult.CompareRow(result) + assert.NoError(t, err, err) + } + } + } +} diff --git a/integration_test/scripts/shadow/init.sql b/integration_test/scripts/shadow/init.sql new file mode 100644 index 00000000..6e0c9ddb --- /dev/null +++ b/integration_test/scripts/shadow/init.sql @@ -0,0 +1,115 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- + +-- Sample employee database +-- See changelog table for details +-- Copyright (C) 2007,2008, MySQL AB +-- +-- Original data created by Fusheng Wang and Carlo Zaniolo +-- http://www.cs.aau.dk/TimeCenter/software.htm +-- http://www.cs.aau.dk/TimeCenter/Data/employeeTemporalDataSet.zip +-- +-- Current schema by Giuseppe Maxia +-- Data conversion from XML to relational by Patrick Crews +-- +-- This work is licensed under the +-- Creative Commons Attribution-Share Alike 3.0 Unported License. +-- To view a copy of this license, visit +-- http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to +-- Creative Commons, 171 Second Street, Suite 300, San Francisco, +-- California, 94105, USA. +-- +-- DISCLAIMER +-- To the best of our knowledge, this data is fabricated, and +-- it does not correspond to real people. +-- Any similarity to existing people is purely coincidental. +-- + +CREATE DATABASE IF NOT EXISTS employees_0000 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS employees_shadow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +USE employees_0000; + +SELECT 'CREATING DATABASE STRUCTURE' as 'INFO'; + +DROP TABLE IF EXISTS dept_emp, + dept_manager, + titles, + salaries, + employees, + departments; + +/*!50503 set default_storage_engine = InnoDB */; +/*!50503 select CONCAT('storage engine: ', @@default_storage_engine) as INFO */; + +CREATE TABLE employees ( + emp_no INT NOT NULL, + birth_date DATE NOT NULL, + first_name VARCHAR(14) NOT NULL, + last_name VARCHAR(16) NOT NULL, + gender ENUM ('M','F') NOT NULL, + hire_date DATE NOT NULL, + PRIMARY KEY (emp_no) +); + +CREATE TABLE departments ( + dept_no CHAR(4) NOT NULL, + dept_name VARCHAR(40) NOT NULL, + PRIMARY KEY (dept_no), + UNIQUE KEY (dept_name) +); + +CREATE TABLE dept_manager ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no,dept_no) +); + +CREATE TABLE dept_emp ( + emp_no INT NOT NULL, + dept_no CHAR(4) NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + FOREIGN KEY (dept_no) REFERENCES departments (dept_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no,dept_no) +); + +CREATE TABLE titles ( + emp_no INT NOT NULL, + title VARCHAR(50) NOT NULL, + from_date DATE NOT NULL, + to_date DATE, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no,title, from_date) +) +; + +CREATE TABLE salaries ( + emp_no INT NOT NULL, + salary INT NOT NULL, + from_date DATE NOT NULL, + to_date DATE NOT NULL, + FOREIGN KEY (emp_no) REFERENCES employees (emp_no) ON DELETE CASCADE, + PRIMARY KEY (emp_no, from_date), + KEY `from_date` (`from_date`) +) +; diff --git a/integration_test/scripts/shadow/sequence.sql b/integration_test/scripts/shadow/sequence.sql new file mode 100644 index 00000000..0df0add1 --- /dev/null +++ b/integration_test/scripts/shadow/sequence.sql @@ -0,0 +1,31 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- + +CREATE DATABASE IF NOT EXISTS employees_0000 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`sequence` +( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(64) NOT NULL, + `value` BIGINT NOT NULL, + `step` INT NOT NULL DEFAULT 10000, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_name` (`name`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; diff --git a/integration_test/scripts/shadow/sharding.sql b/integration_test/scripts/shadow/sharding.sql new file mode 100644 index 00000000..40caa0c9 --- /dev/null +++ b/integration_test/scripts/shadow/sharding.sql @@ -0,0 +1,1178 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You 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. +-- + +CREATE DATABASE IF NOT EXISTS employees_0000 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS employees_0001 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS employees_0002 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS employees_0003 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE DATABASE IF NOT EXISTS employees_0000_r CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0000` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0001` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0002` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0003` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0004` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0005` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0006` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0007` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0008` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0009` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0010` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0011` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0012` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0013` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0014` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0015` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0016` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0017` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0018` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0019` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0020` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0021` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0022` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0023` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0024` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0025` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0026` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0027` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0028` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0029` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0030` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0031` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0000` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0001` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0002` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0003` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0004` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0005` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0006` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0007` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0000` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0001` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0002` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0003` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0004` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0005` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0006` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0007` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0008` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0009` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0010` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0011` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0012` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0013` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0014` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0015` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0016` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0017` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0018` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0019` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0020` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0021` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0022` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0023` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0024` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0025` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0026` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0027` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0028` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0029` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0030` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `employees_shadow`.`student_0031` +( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `uid` BIGINT(20) UNSIGNED NOT NULL, + `name` VARCHAR(255) NOT NULL, + `score` DECIMAL(6,2) DEFAULT '0', + `nickname` VARCHAR(255) DEFAULT NULL, + `gender` TINYINT(4) NULL, + `birth_year` SMALLINT(5) UNSIGNED DEFAULT '0', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uid` (`uid`), + KEY `nickname` (`nickname`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO employees_0000.student_0001(id,uid,name,score,nickname,gender,birth_year,created_at,modified_at) VALUES (1, 1, 'arana', 95, 'Awesome Arana', 0, 2021, NOW(), NOW()); diff --git a/integration_test/testcase/casetest.yaml b/integration_test/testcase/casetest.yaml index 0324d827..10eca060 100644 --- a/integration_test/testcase/casetest.yaml +++ b/integration_test/testcase/casetest.yaml @@ -25,6 +25,14 @@ query_rows_cases: expected: type: "file" value: "../../config/db/expected.yaml" + - sql: "/*A! shadow(shadow) */ SELECT name FROM student WHERE name= ?" + parameters: "1:string" + sense: + - shadow + expected: + type: "file" + value: "../../config/db/expected.yaml" + query_row_cases: - sql: "SELECT COUNT(1) FROM sequence WHERE name=?" parameters: "1:string" @@ -43,3 +51,23 @@ exec_cases: expected: type: "rowAffect" value: 1 + - sql: "/*A! shadow(shadow) */ INSERT INTO student(id,uid,score,name,nickname,gender,birth_year) values (?,?,?,?,?,?,?)" + parameters: "1:int, 2:int, 100:int, test:string, test:string, 1:int, 1980:int" + sense: + - shadow + expected: + type: "rowAffect" + value: 1 + - sql: "/*A! shadow(shadow) */ update student set score=100.0 where uid = ?" + parameters: "2:int" + sense: + - shadow + expected: + type: "rowAffect" + value: 1 + - sql: "/*A! shadow(shadow) */ delete from student" + sense: + - shadow + expected: + type: "rowAffect" + value: 0 diff --git a/pkg/boot/boot.go b/pkg/boot/boot.go index 683518f5..f07d7a6f 100644 --- a/pkg/boot/boot.go +++ b/pkg/boot/boot.go @@ -145,7 +145,21 @@ func buildNamespace(ctx context.Context, provider Discovery, clusterName string) } ru.SetVTable(table, vt) } - initCmds = append(initCmds, namespace.UpdateRule(&ru)) + + var su = rule.NewShadowRule() + for _, table := range tables { + var ruleManager rule.ShadowRuleManager + if ruleManager, err = provider.GetShadowOperation(ctx, clusterName, table); err != nil { + return nil, err + } + if ruleManager == nil { + log.Warnf("no such shadow rule %s", table) + continue + } + su.SetRuleManager(table, ruleManager) + } + + initCmds = append(initCmds, namespace.UpdateRule(&ru), namespace.UpdateShadowRule(su)) return namespace.New(clusterName, initCmds...) } diff --git a/pkg/boot/discovery.go b/pkg/boot/discovery.go index a3a0e9d0..25bc0d4e 100644 --- a/pkg/boot/discovery.go +++ b/pkg/boot/discovery.go @@ -105,6 +105,8 @@ type Discovery interface { ListTables(ctx context.Context, cluster string) ([]string, error) // GetTable returns the table info. GetTable(ctx context.Context, cluster, table string) (*rule.VTable, error) + // GetShadowOperation return the shadow info. + GetShadowOperation(ctx context.Context, cluster, table string) (rule.ShadowRuleManager, error) // GetConfigCenter GetConfigCenter() *config.Center @@ -116,6 +118,45 @@ type discovery struct { c *config.Center } +func (fp *discovery) GetShadowOperation(ctx context.Context, cluster, table string) (rule.ShadowRuleManager, error) { + cfg, err := fp.c.Load() + if err != nil { + return nil, err + } + + if cfg.Data.ShadowRule == nil { + return nil, nil + } + + for _, tbl := range cfg.Data.ShadowRule.ShadowTables { + db, tb, err := parseTable(tbl.Name) + if err != nil { + log.Warnf("skip parsing table rule: %v", err) + continue + } + if db != cluster { + continue + } + if tb != table { + continue + } + + operations := make(map[string][]*rule.Attribute, len(tbl.MatchRules)*2) + for _, matchRule := range tbl.MatchRules { + attributes := make([]*rule.Attribute, 0, len(matchRule.Attributes)) + for _, attr := range matchRule.Attributes { + attributes = append(attributes, rule.NewAttribute(attr.Column, attr.Value, matchRule.MatchType)) + } + for _, operation := range matchRule.Operation { + operations[operation] = attributes + } + } + return rule.NewRuleManager(operations, tbl.Enable, tbl.GroupNode), nil + } + + return nil, nil +} + func (fp *discovery) Init(ctx context.Context) error { if err := fp.loadBootOptions(); err != nil { return err @@ -285,12 +326,21 @@ func (fp *discovery) ListTables(ctx context.Context, cluster string) ([]string, return tables, nil } -func (fp *discovery) GetNode(ctx context.Context, cluster, group, node string) (*config.Node, error) { +func (fp *discovery) GetNodes(ctx context.Context, cluster, group string) ([]*config.Node, error) { bingo, ok := fp.loadGroup(cluster, group) if !ok { return nil, nil } - for _, it := range bingo.Nodes { + + return bingo.Nodes, nil +} + +func (fp *discovery) GetNode(ctx context.Context, cluster, group, node string) (*config.Node, error) { + nodes, err := fp.GetNodes(ctx, cluster, group) + if err != nil { + return nil, err + } + for _, it := range nodes { if it.Name == node { return it, nil } diff --git a/pkg/config/api.go b/pkg/config/api.go index 08105ac0..dd73b47a 100644 --- a/pkg/config/api.go +++ b/pkg/config/api.go @@ -39,6 +39,7 @@ const ( DefaultConfigDataSourceClustersPath PathKey = "/arana-db/config/data/dataSourceClusters" DefaultConfigDataShardingRulePath PathKey = "/arana-db/config/data/shardingRule" DefaultConfigDataTenantsPath PathKey = "/arana-db/config/data/tenants" + DefaultConfigDataShadowRulePath PathKey = "/arana-db/config/data/shadowRule" ) const ( diff --git a/pkg/config/config.go b/pkg/config/config.go index e1343975..8aa228c4 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -48,6 +48,7 @@ var ( DefaultConfigDataListenersPath: "data.listeners", DefaultConfigDataSourceClustersPath: "data.clusters", DefaultConfigDataShardingRulePath: "data.sharding_rule", + DefaultConfigDataShadowRulePath: "data.shadow_rule", } _configValSupplier map[PathKey]func(cfg *Configuration) interface{} = map[PathKey]func(cfg *Configuration) interface{}{ @@ -69,6 +70,9 @@ var ( DefaultConfigDataShardingRulePath: func(cfg *Configuration) interface{} { return &cfg.Data.ShardingRule }, + DefaultConfigDataShadowRulePath: func(cfg *Configuration) interface{} { + return &cfg.Data.ShadowRule + }, } ) @@ -163,6 +167,9 @@ func (c *Center) loadFromStore(ctx context.Context) (*Configuration, error) { Tenants: make([]*Tenant, 0), DataSourceClusters: make([]*DataSourceCluster, 0), ShardingRule: &ShardingRule{}, + ShadowRule: &ShadowRule{ + ShadowTables: make([]*ShadowTable, 0), + }, }, } diff --git a/pkg/constants/const.go b/pkg/constants/const.go index caeb5029..e4695f1b 100644 --- a/pkg/constants/const.go +++ b/pkg/constants/const.go @@ -27,3 +27,14 @@ const ( VariableNameMaxAllowedPacket = "max_allowed_packet" ) + +const ( + ShadowMatchRegex = "regex" + ShadowMatchValue = "value" + ShadowMatchHint = "hint" + + ShadowInsert = "insert" + ShadowSelect = "select" + ShadowUpdate = "update" + ShadowDelete = "delete" +) diff --git a/pkg/proto/hint/hint.go b/pkg/proto/hint/hint.go index 2b6cb905..d15ddfdf 100644 --- a/pkg/proto/hint/hint.go +++ b/pkg/proto/hint/hint.go @@ -38,6 +38,7 @@ const ( TypeRoute // custom route TypeFullScan // enable full-scan TypeDirect // direct route + TypeShadow // shadow route ) var _hintTypes = [...]string{ @@ -46,6 +47,7 @@ var _hintTypes = [...]string{ TypeRoute: "ROUTE", TypeFullScan: "FULLSCAN", TypeDirect: "DIRECT", + TypeShadow: "SHADOW", } // KeyValue represents a pair of key and value. diff --git a/pkg/proto/rule/database_table.go b/pkg/proto/rule/database_table.go index ebea894f..2f3281c0 100644 --- a/pkg/proto/rule/database_table.go +++ b/pkg/proto/rule/database_table.go @@ -328,3 +328,16 @@ func (dt DatabaseTables) String() string { sb.WriteByte(']') return sb.String() } + +func (dt DatabaseTables) ReplaceDb(new string) { + if dt.IsEmpty() { + return + } + newTbls := make([]string, 0) + for db, tbls := range dt { + newTbls = append(newTbls, tbls...) + delete(dt, db) + } + + dt[new] = newTbls +} diff --git a/pkg/proto/rule/database_table_test.go b/pkg/proto/rule/database_table_test.go index 5445d25d..50851557 100644 --- a/pkg/proto/rule/database_table_test.go +++ b/pkg/proto/rule/database_table_test.go @@ -152,3 +152,22 @@ func TestDatabaseTables_Smallest(t *testing.T) { }) } } + +func TestDatabaseTables_Replace(t *testing.T) { + type tt struct { + input string + expectTbl string + } + + for _, it := range []tt{ + {"db0:tb0,tb1;db1:tb2,tb3;db2:tb4,tb5", "tb0"}, + {"db2:tb0,tb1;db1:tb2,tb3;db0:tb4,tb5", "tb0"}, + } { + t.Run(it.input, func(t *testing.T) { + dt := parseDatabaseTablesFromString(it.input) + dt.ReplaceDb("shadow") + assert.Equal(t, 6, len(dt["shadow"])) + assert.Equal(t, 1, len(dt)) + }) + } +} diff --git a/pkg/proto/rule/shadow.go b/pkg/proto/rule/shadow.go new file mode 100644 index 00000000..79fceb6a --- /dev/null +++ b/pkg/proto/rule/shadow.go @@ -0,0 +1,172 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 rule + +import ( + "sync" +) + +import ( + "github.com/arana-db/arana/pkg/constants" +) + +type ShadowRuleManager interface { + MatchValueBy(action, column, value string) bool + MatchHintBy(action, hint string) bool + MatchRegexBy(action, column, value string) bool + GetDatabase() string +} + +// ShadowRule represents the shadow of databases and tables. +type ShadowRule struct { + mu sync.RWMutex + rules map[string]ShadowRuleManager // map[table]rule +} + +func (s *ShadowRule) MatchValueBy(tableName, action, column, value string) bool { + s.mu.RLock() + defer s.mu.RUnlock() + rule, ok := s.rules[tableName] + if !ok { + return false + } + + return rule.MatchValueBy(action, column, value) +} + +func (s *ShadowRule) MatchHintBy(tableName, action, hint string) bool { + s.mu.RLock() + defer s.mu.RUnlock() + rule, ok := s.rules[tableName] + if !ok { + return false + } + return rule.MatchHintBy(action, hint) +} + +func (s *ShadowRule) MatchRegexBy(tableName, action, column, value string) bool { + s.mu.RLock() + defer s.mu.RUnlock() + + rule, ok := s.rules[tableName] + if !ok { + return false + } + return rule.MatchRegexBy(action, column, value) +} + +func (s *ShadowRule) GetDatabase(tableName string) string { + s.mu.RLock() + defer s.mu.RUnlock() + + return s.rules[tableName].GetDatabase() +} + +func (s *ShadowRule) SetRuleManager(tableName string, ruleManager ShadowRuleManager) { + s.mu.Lock() + if s.rules == nil { + s.rules = make(map[string]ShadowRuleManager, 10) + } + s.rules[tableName] = ruleManager + s.mu.Unlock() +} + +func NewShadowRule() *ShadowRule { + return &ShadowRule{rules: make(map[string]ShadowRuleManager, 0)} +} + +type Operation struct { + enable bool + database string + actions map[string][]*Attribute // map[action][]*Attribute, action in (select, update, delete, update) +} + +func (o *Operation) GetDatabase() string { + return o.database +} + +func (o *Operation) MatchValueBy(action, column, value string) bool { + if !o.enable { + return false + } + attrs, ok := o.actions[action] + if !ok { + return false + } + + for _, attr := range attrs { + if attr.typ == constants.ShadowMatchValue && attr.column == column && attr.value == value { + return true + } + } + return false +} + +func (o *Operation) MatchHintBy(action, hint string) bool { + if !o.enable { + return false + } + attrs, ok := o.actions[action] + if !ok { + return false + } + + for _, attr := range attrs { + if attr.typ == constants.ShadowMatchHint { + return attr.value == hint + } + } + return false +} + +// MatchRegexBy . +// TODO impl match regex rule +func (o *Operation) MatchRegexBy(action, column, value string) bool { + if !o.enable { + return false + } + _, ok := o.actions[action] + if !ok { + return false + } + // TODO impl regex rule below + + return false +} + +func NewRuleManager(actions map[string][]*Attribute, enable bool, database string) ShadowRuleManager { + return &Operation{ + actions: actions, + database: database, + enable: enable, + } +} + +type Attribute struct { + column string + value string + typ string // regex, value, hint +} + +func NewAttribute(col, val, typ string) *Attribute { + return &Attribute{ + column: col, + typ: typ, + value: val, + } +} diff --git a/pkg/runtime/namespace/command.go b/pkg/runtime/namespace/command.go index 54a9209b..98795fe5 100644 --- a/pkg/runtime/namespace/command.go +++ b/pkg/runtime/namespace/command.go @@ -154,3 +154,14 @@ func UpdateRule(rule *rule.Rule) Command { return nil } } + +// UpdateShadowRule updates the shadow rule. +func UpdateShadowRule(rule *rule.ShadowRule) Command { + return func(ns *Namespace) error { + ns.Lock() + defer ns.Unlock() + ns.shadowRule.Store(rule) + + return nil + } +} diff --git a/pkg/runtime/namespace/namespace.go b/pkg/runtime/namespace/namespace.go index 799fcaeb..e5a76994 100644 --- a/pkg/runtime/namespace/namespace.go +++ b/pkg/runtime/namespace/namespace.go @@ -76,7 +76,8 @@ type ( name string // the name of Namespace - rule atomic.Value // *rule.Rule + rule atomic.Value // *rule.Rule + shadowRule atomic.Value // *rule.ShadowRule // datasource map, eg: employee_0001 -> [mysql-a,mysql-b,mysql-c], ... employee_0007 -> [mysql-x,mysql-y,mysql-z] dss atomic.Value // map[string][]proto.DB @@ -98,6 +99,7 @@ func New(name string, commands ...Command) (*Namespace, error) { } ns.dss.Store(make(map[string][]proto.DB)) // init empty map ns.rule.Store(&rule.Rule{}) // init empty rule + ns.shadowRule.Store(&rule.ShadowRule{}) // init empty shadowRule for _, cmd := range commands { if err := cmd(ns); err != nil { @@ -224,6 +226,15 @@ func (ns *Namespace) Rule() *rule.Rule { return ru } +// ShadowRule returns the shadow rule. +func (ns *Namespace) ShadowRule() *rule.ShadowRule { + su, ok := ns.shadowRule.Load().(*rule.ShadowRule) + if !ok { + return nil + } + return su +} + // EnqueueCommand enqueues the next command, it will be executed async. func (ns *Namespace) EnqueueCommand(cmd Command) error { if ns.closed.Load() { diff --git a/pkg/runtime/optimize/dml/delete.go b/pkg/runtime/optimize/dml/delete.go index 39960fa6..fc69751a 100644 --- a/pkg/runtime/optimize/dml/delete.go +++ b/pkg/runtime/optimize/dml/delete.go @@ -26,6 +26,7 @@ import ( ) import ( + "github.com/arana-db/arana/pkg/constants" "github.com/arana-db/arana/pkg/proto" "github.com/arana-db/arana/pkg/runtime/ast" "github.com/arana-db/arana/pkg/runtime/optimize" @@ -45,10 +46,27 @@ func optimizeDelete(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err return nil, errors.Wrap(err, "failed to optimize DELETE statement") } + var matchShadow bool + if len(o.Hints) > 0 { + shadowLoader, err := optimize.Hints(stmt.Table, o.Hints, o.Rule, o.ShadowRule) + if err != nil { + return nil, errors.Wrap(err, "failed to optimize hint DELETE statement") + } + matchShadow = shadowLoader.GetMatchBy(stmt.Table.Suffix(), constants.ShadowDelete) + } + // TODO: delete from a child sharding-table directly if shards == nil { - return plan.Transparent(stmt, o.Args), nil + transparent := plan.Transparent(stmt, o.Args) + if matchShadow { + transparent.SetDB(o.ShadowRule.GetDatabase(stmt.Table.Suffix())) + } + return transparent, nil + } + + if matchShadow { + shards.ReplaceDb(o.ShadowRule.GetDatabase(stmt.Table.Suffix())) } ret := dml.NewSimpleDeletePlan(stmt) diff --git a/pkg/runtime/optimize/dml/insert.go b/pkg/runtime/optimize/dml/insert.go index 8338f078..3ac6578f 100644 --- a/pkg/runtime/optimize/dml/insert.go +++ b/pkg/runtime/optimize/dml/insert.go @@ -26,6 +26,7 @@ import ( ) import ( + "github.com/arana-db/arana/pkg/constants" "github.com/arana-db/arana/pkg/proto" "github.com/arana-db/arana/pkg/proto/rule" "github.com/arana-db/arana/pkg/runtime/ast" @@ -45,11 +46,12 @@ func optimizeInsert(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err ret.BindArgs(o.Args) var ( - stmt = o.Stmt.(*ast.InsertStatement) - vt *rule.VTable - ok bool - tableName = stmt.Table - err error + stmt = o.Stmt.(*ast.InsertStatement) + vt *rule.VTable + ok bool + tableName = stmt.Table + err error + matchShadow bool ) if vt, ok = o.Rule.VTable(stmt.Table.Suffix()); !ok { // insert into non-sharding table @@ -105,9 +107,13 @@ func optimizeInsert(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err resetFilter(stmt.Columns[bingo], value) if len(o.Hints) > 0 { - if shards, err = optimize.Hints(tableName, o.Hints, o.Rule); err != nil { + var hintLoader optimize.HintResultLoader + if hintLoader, err = optimize.Hints(tableName, o.Hints, o.Rule, o.ShadowRule); err != nil { return nil, errors.Wrap(err, "calculate hints failed") } + + shards = hintLoader.GetShards() + matchShadow = hintLoader.GetMatchBy(tableName.Suffix(), constants.ShadowInsert) } if shards == nil { @@ -126,6 +132,9 @@ func optimizeInsert(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err ) for k, v := range shards { + if matchShadow { + k = o.ShadowRule.GetDatabase(tableName.Suffix()) + } db = k table = v[0] break @@ -138,6 +147,9 @@ func optimizeInsert(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err } for db, slot := range slots { + if matchShadow { + db = o.ShadowRule.GetDatabase(tableName.Suffix()) + } for table, indexes := range slot { // clone insert stmt without values newborn := ast.NewInsertStatement(ast.TableName{table}, stmt.Columns) diff --git a/pkg/runtime/optimize/dml/select.go b/pkg/runtime/optimize/dml/select.go index 17757a80..a720a935 100644 --- a/pkg/runtime/optimize/dml/select.go +++ b/pkg/runtime/optimize/dml/select.go @@ -27,6 +27,7 @@ import ( ) import ( + "github.com/arana-db/arana/pkg/constants" "github.com/arana-db/arana/pkg/dataset" "github.com/arana-db/arana/pkg/merge/aggregator" "github.com/arana-db/arana/pkg/proto" @@ -78,16 +79,22 @@ func optimizeSelect(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err // --- SIMPLE QUERY BEGIN --- var ( - shards rule.DatabaseTables - fullScan bool - err error - vt = o.Rule.MustVTable(stmt.From[0].TableName().Suffix()) - tableName = stmt.From[0].TableName() + shards rule.DatabaseTables + fullScan bool + matchShadow bool + err error + vt = o.Rule.MustVTable(stmt.From[0].TableName().Suffix()) + tableName = stmt.From[0].TableName() ) + if len(o.Hints) > 0 { - if shards, err = optimize.Hints(tableName, o.Hints, o.Rule); err != nil { + var hintLoader optimize.HintResultLoader + if hintLoader, err = optimize.Hints(tableName, o.Hints, o.Rule, o.ShadowRule); err != nil { return nil, errors.Wrap(err, "calculate hints failed") } + + shards = hintLoader.GetShards() + matchShadow = hintLoader.GetMatchBy(tableName.Suffix(), constants.ShadowSelect) } if shards == nil { @@ -129,6 +136,9 @@ func optimizeSelect(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err return nil, errors.Errorf("cannot compute minimal topology from '%s'", stmt.From[0].TableName().Suffix()) } + if matchShadow { + db0 = o.ShadowRule.GetDatabase(tableName.Suffix()) + } return toSingle(db0, tbl0) } @@ -137,6 +147,9 @@ func optimizeSelect(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err var db, tbl string for k, v := range shards { db = k + if matchShadow { + db = o.ShadowRule.GetDatabase(tableName.Suffix()) + } tbl = v[0] } return toSingle(db, tbl) @@ -164,6 +177,9 @@ func optimizeSelect(ctx context.Context, o *optimize.Optimizer) (proto.Plan, err plans := make([]proto.Plan, 0, len(shards)) for k, v := range shards { + if matchShadow { + k = o.ShadowRule.GetDatabase(tableName.Suffix()) + } next := &dml.SimpleQueryPlan{ Database: k, Tables: v, diff --git a/pkg/runtime/optimize/dml/update.go b/pkg/runtime/optimize/dml/update.go index a48ed446..195c6f41 100644 --- a/pkg/runtime/optimize/dml/update.go +++ b/pkg/runtime/optimize/dml/update.go @@ -26,6 +26,7 @@ import ( ) import ( + "github.com/arana-db/arana/pkg/constants" "github.com/arana-db/arana/pkg/proto" "github.com/arana-db/arana/pkg/proto/rule" "github.com/arana-db/arana/pkg/runtime/ast" @@ -61,17 +62,21 @@ func optimizeUpdate(_ context.Context, o *optimize.Optimizer) (proto.Plan, error } var ( - shards rule.DatabaseTables - fullScan = true - err error + shards rule.DatabaseTables + fullScan = true + err error + matchShadow bool ) // compute shards if where := stmt.Where; where != nil { if len(o.Hints) > 0 { - if shards, err = optimize.Hints(table, o.Hints, o.Rule); err != nil { + var hintLoader optimize.HintResultLoader + if hintLoader, err = optimize.Hints(table, o.Hints, o.Rule, o.ShadowRule); err != nil { return nil, errors.Wrap(err, "calculate hints failed") } + shards = hintLoader.GetShards() + matchShadow = hintLoader.GetMatchBy(table.Suffix(), constants.ShadowUpdate) } if shards == nil { @@ -97,6 +102,10 @@ func optimizeUpdate(_ context.Context, o *optimize.Optimizer) (proto.Plan, error shards = vt.Topology().Enumerate() } + if matchShadow { + shards.ReplaceDb(o.ShadowRule.GetDatabase(stmt.Table.Suffix())) + } + ret := dml.NewUpdatePlan(stmt) ret.BindArgs(o.Args) ret.SetShards(shards) diff --git a/pkg/runtime/optimize/hints.go b/pkg/runtime/optimize/hints.go index f24c4ca6..d7d1e164 100644 --- a/pkg/runtime/optimize/hints.go +++ b/pkg/runtime/optimize/hints.go @@ -34,10 +34,16 @@ import ( func init() { RegisterHint(hint.TypeDirect, &Direct{}) RegisterHint(hint.TypeRoute, &CustomRoute{}) + RegisterHint(hint.TypeShadow, &Shadow{}) } type HintExecutor interface { - exec(tableName ast.TableName, rule *rule.Rule, hints []*hint.Hint) (hintTables rule.DatabaseTables, err error) + exec(tableName ast.TableName, rule *rule.Rule, su *rule.ShadowRule, hints []*hint.Hint) (HintResultLoader, error) +} + +type HintResultLoader interface { + GetShards() rule.DatabaseTables + GetMatchBy(tableName, action string) bool } var hintHandlers = make(map[hint.Type]HintExecutor) @@ -78,13 +84,15 @@ func validate(hints []*hint.Hint) error { return nil } -func Hints(tableName ast.TableName, hints []*hint.Hint, rule *rule.Rule) (hintTables rule.DatabaseTables, err error) { - if err = validate(hints); err != nil { - return +func Hints(tableName ast.TableName, hints []*hint.Hint, rule *rule.Rule, su *rule.ShadowRule) (HintResultLoader, error) { + if err := validate(hints); err != nil { + return nil, err } var ( - executor HintExecutor - ok bool + executor HintExecutor + ok bool + hintResult HintResultLoader + err error ) for _, v := range hints { if executor, ok = hintHandlers[v.Type]; ok { @@ -92,32 +100,71 @@ func Hints(tableName ast.TableName, hints []*hint.Hint, rule *rule.Rule) (hintTa } } if executor == nil { - return + return nil, nil } - if hintTables, err = executor.exec(tableName, rule, hints); err != nil { - return + if hintResult, err = executor.exec(tableName, rule, su, hints); err != nil { + return nil, err + } + return hintResult, nil +} + +type Shadow struct{} + +func (s *Shadow) exec(tableName ast.TableName, ru *rule.Rule, su *rule.ShadowRule, hints []*hint.Hint) (HintResultLoader, error) { + shadowLabels := make([]string, 0) + for _, h := range hints { + if h.Type != hint.TypeShadow { + continue + } + for _, i := range h.Inputs { + shadowLabels = append(shadowLabels, i.V) + } } - return + return &hintInfo{ + tbls: make(rule.DatabaseTables, 0), + shadowLabels: shadowLabels, + shadowRule: su, + }, nil } // Direct force forward to db[0] type Direct struct{} -func (h *Direct) exec(tableName ast.TableName, rule *rule.Rule, hints []*hint.Hint) (hintTables rule.DatabaseTables, err error) { - db0, _, ok := rule.MustVTable(tableName.Suffix()).Topology().Smallest() +func (h *Direct) exec(tableName ast.TableName, ru *rule.Rule, su *rule.ShadowRule, hints []*hint.Hint) (HintResultLoader, error) { + db0, _, ok := ru.MustVTable(tableName.Suffix()).Topology().Smallest() if !ok { return nil, errors.New("not found db0") } - hintTables = make(map[string][]string, 1) + hintTables := make(rule.DatabaseTables, 1) hintTables[db0] = []string{tableName.String()} - return hintTables, nil + shadowLabels := make([]string, 0) + for _, ht := range hints { + if ht.Type != hint.TypeShadow { + continue + } + for _, i := range ht.Inputs { + shadowLabels = append(shadowLabels, i.V) + } + } + return &hintInfo{ + tbls: hintTables, + shadowRule: su, + shadowLabels: shadowLabels, + }, nil } type CustomRoute struct{} -func (c *CustomRoute) exec(tableName ast.TableName, r *rule.Rule, hints []*hint.Hint) (hintTables rule.DatabaseTables, err error) { - hintTables = make(map[string][]string) +func (c *CustomRoute) exec(tableName ast.TableName, ru *rule.Rule, su *rule.ShadowRule, hints []*hint.Hint) (HintResultLoader, error) { + hintTables := make(rule.DatabaseTables) + shadowLabels := make([]string, 0) for _, h := range hints { + if h.Type == hint.TypeShadow { + for _, i := range h.Inputs { + shadowLabels = append(shadowLabels, i.V) + } + } + if h.Type != hint.TypeRoute { continue } @@ -126,5 +173,32 @@ func (c *CustomRoute) exec(tableName ast.TableName, r *rule.Rule, hints []*hint. hintTables[tb[0]] = append(hintTables[tb[0]], tb[1]) } } - return + + return &hintInfo{ + tbls: hintTables, + shadowRule: su, + shadowLabels: shadowLabels, + }, nil +} + +type hintInfo struct { + tbls rule.DatabaseTables + shadowRule *rule.ShadowRule + shadowLabels []string +} + +func (h *hintInfo) GetShards() rule.DatabaseTables { + return h.tbls +} + +func (h *hintInfo) GetMatchBy(tableName, action string) bool { + if h.shadowRule == nil { + return false + } + for _, shadowHint := range h.shadowLabels { + if h.shadowRule.MatchHintBy(tableName, action, shadowHint) { + return true + } + } + return false } diff --git a/pkg/runtime/optimize/optimizer.go b/pkg/runtime/optimize/optimizer.go index 5147eea7..0e2e50f8 100644 --- a/pkg/runtime/optimize/optimizer.go +++ b/pkg/runtime/optimize/optimizer.go @@ -74,13 +74,14 @@ func Register(t rast.SQLType, h Processor) { type Processor = func(ctx context.Context, o *Optimizer) (proto.Plan, error) type Optimizer struct { - Rule *rule.Rule - Hints []*hint.Hint - Stmt rast.Statement - Args []interface{} + Rule *rule.Rule + ShadowRule *rule.ShadowRule + Hints []*hint.Hint + Stmt rast.Statement + Args []interface{} } -func NewOptimizer(rule *rule.Rule, hints []*hint.Hint, stmt ast.StmtNode, args []interface{}) (proto.Optimizer, error) { +func NewOptimizer(rule *rule.Rule, su *rule.ShadowRule, hints []*hint.Hint, stmt ast.StmtNode, args []interface{}) (proto.Optimizer, error) { var ( rstmt rast.Statement err error @@ -90,10 +91,11 @@ func NewOptimizer(rule *rule.Rule, hints []*hint.Hint, stmt ast.StmtNode, args [ } return &Optimizer{ - Rule: rule, - Hints: hints, - Stmt: rstmt, - Args: args, + Rule: rule, + ShadowRule: su, + Hints: hints, + Stmt: rstmt, + Args: args, }, nil } @@ -122,17 +124,18 @@ func (o *Optimizer) ComputeShards(table rast.TableName, where rast.ExpressionNod return nil, nil } var ( - shards rule.DatabaseTables - err error - fullScan bool + shards rule.DatabaseTables + hintLoader HintResultLoader + err error + fullScan bool ) if len(o.Hints) > 0 { - if shards, err = Hints(table, o.Hints, o.Rule); err != nil { + if hintLoader, err = Hints(table, o.Hints, o.Rule, o.ShadowRule); err != nil { return nil, perrors.Wrap(err, "calculate hints failed") } } - + shards = hintLoader.GetShards() if shards == nil { if shards, fullScan, err = (*Sharder)(ru).Shard(table, where, args...); err != nil { return nil, perrors.Wrapf(err, "optimize: cannot calculate shards of table '%s'", table.Suffix()) diff --git a/pkg/runtime/optimize/optimizer_test.go b/pkg/runtime/optimize/optimizer_test.go index 2dd25470..5e11dc4a 100644 --- a/pkg/runtime/optimize/optimizer_test.go +++ b/pkg/runtime/optimize/optimizer_test.go @@ -65,11 +65,12 @@ func TestOptimizer_OptimizeSelect(t *testing.T) { sql = "select id, uid from student where uid in (?,?,?)" ctx = context.Background() ru = makeFakeRule(ctrl, 8) + su = makeFakeShadowRule(ctrl) ) p := parser.New() stmt, _ := p.ParseOneStmt(sql, "", "") - opt, err := NewOptimizer(ru, nil, stmt, []interface{}{1, 2, 3}) + opt, err := NewOptimizer(ru, su, nil, stmt, []interface{}{1, 2, 3}) assert.NoError(t, err) plan, err := opt.Optimize(ctx) assert.NoError(t, err) @@ -140,6 +141,7 @@ func TestOptimizer_OptimizeInsert(t *testing.T) { var ( ctx = context.Background() ru = makeFakeRule(ctrl, 8) + su = makeFakeShadowRule(ctrl) ) t.Run("sharding", func(t *testing.T) { @@ -148,7 +150,7 @@ func TestOptimizer_OptimizeInsert(t *testing.T) { p := parser.New() stmt, _ := p.ParseOneStmt(sql, "", "") - opt, err := NewOptimizer(ru, nil, stmt, []interface{}{8, 9, 16}) + opt, err := NewOptimizer(ru, su, nil, stmt, []interface{}{8, 9, 16}) assert.NoError(t, err) plan, err := opt.Optimize(ctx) // 8,16 -> fake_db_0000, 9 -> fake_db_0001 @@ -169,7 +171,7 @@ func TestOptimizer_OptimizeInsert(t *testing.T) { p := parser.New() stmt, _ := p.ParseOneStmt(sql, "", "") - opt, err := NewOptimizer(ru, nil, stmt, []interface{}{1}) + opt, err := NewOptimizer(ru, su, nil, stmt, []interface{}{1}) assert.NoError(t, err) plan, err := opt.Optimize(ctx) @@ -200,6 +202,7 @@ func TestOptimizer_OptimizeAlterTable(t *testing.T) { var ( ctx = context.Background() ru rule.Rule + su rule.ShadowRule tab rule.VTable topology rule.Topology ) @@ -224,7 +227,7 @@ func TestOptimizer_OptimizeAlterTable(t *testing.T) { p := parser.New() stmt, _ := p.ParseOneStmt(sql, "", "") - opt, err := NewOptimizer(&ru, nil, stmt, nil) + opt, err := NewOptimizer(&ru, &su, nil, stmt, nil) assert.NoError(t, err) plan, err := opt.Optimize(ctx) @@ -240,7 +243,7 @@ func TestOptimizer_OptimizeAlterTable(t *testing.T) { p := parser.New() stmt, _ := p.ParseOneStmt(sql, "", "") - opt, err := NewOptimizer(&ru, nil, stmt, nil) + opt, err := NewOptimizer(&ru, &su, nil, stmt, nil) assert.NoError(t, err) plan, err := opt.Optimize(ctx) @@ -273,6 +276,7 @@ func TestOptimizer_OptimizeInsertSelect(t *testing.T) { var ( ctx = context.Background() ru rule.Rule + su rule.ShadowRule ) ru.SetVTable("student", nil) @@ -283,7 +287,7 @@ func TestOptimizer_OptimizeInsertSelect(t *testing.T) { p := parser.New() stmt, _ := p.ParseOneStmt(sql, "", "") - opt, err := NewOptimizer(&ru, nil, stmt, []interface{}{1}) + opt, err := NewOptimizer(&ru, &su, nil, stmt, []interface{}{1}) assert.NoError(t, err) plan, err := opt.Optimize(ctx) diff --git a/pkg/runtime/optimize/sharder_test.go b/pkg/runtime/optimize/sharder_test.go index 5cac09bd..28219b66 100644 --- a/pkg/runtime/optimize/sharder_test.go +++ b/pkg/runtime/optimize/sharder_test.go @@ -124,3 +124,11 @@ func makeFakeRule(c *gomock.Controller, mod int) *rule.Rule { ru.SetVTable("student", &tab) return &ru } + +func makeFakeShadowRule(c *gomock.Controller) *rule.ShadowRule { + var ( + ru rule.ShadowRule + ) + + return &ru +} diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 44b0f692..815919c2 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -218,6 +218,7 @@ func (tx *compositeTx) Execute(ctx *proto.Context) (res proto.Result, warn uint1 var ( ru = tx.rt.Namespace().Rule() + su = tx.rt.Namespace().ShadowRule() plan proto.Plan c = ctx.Context ) @@ -226,7 +227,7 @@ func (tx *compositeTx) Execute(ctx *proto.Context) (res proto.Result, warn uint1 c = rcontext.WithHints(c, ctx.Stmt.Hints) var opt proto.Optimizer - if opt, err = optimize.NewOptimizer(ru, ctx.Stmt.Hints, ctx.Stmt.StmtNode, args); err != nil { + if opt, err = optimize.NewOptimizer(ru, su, ctx.Stmt.Hints, ctx.Stmt.StmtNode, args); err != nil { err = perrors.WithStack(err) return } @@ -628,6 +629,7 @@ func (pi *defaultRuntime) Execute(ctx *proto.Context) (res proto.Result, warn ui var ( ru = pi.Namespace().Rule() + su = pi.Namespace().ShadowRule() plan proto.Plan c = ctx.Context ) @@ -640,7 +642,7 @@ func (pi *defaultRuntime) Execute(ctx *proto.Context) (res proto.Result, warn ui start := time.Now() var opt proto.Optimizer - if opt, err = optimize.NewOptimizer(ru, ctx.Stmt.Hints, ctx.Stmt.StmtNode, args); err != nil { + if opt, err = optimize.NewOptimizer(ru, su, ctx.Stmt.Hints, ctx.Stmt.StmtNode, args); err != nil { err = perrors.WithStack(err) return } diff --git a/scripts/init.sql b/scripts/init.sql index d40ed699..6e0c9ddb 100644 --- a/scripts/init.sql +++ b/scripts/init.sql @@ -40,7 +40,7 @@ -- CREATE DATABASE IF NOT EXISTS employees_0000 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -CREATE DATABASE IF NOT EXISTS employees_show CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS employees_shadow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE employees_0000; diff --git a/scripts/sharding.sql b/scripts/sharding.sql index d36edda0..28238d25 100644 --- a/scripts/sharding.sql +++ b/scripts/sharding.sql @@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0000` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0001` @@ -51,7 +51,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0001` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0002` @@ -67,7 +67,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0002` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0003` @@ -83,7 +83,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0003` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0004` @@ -99,7 +99,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0004` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0005` @@ -115,7 +115,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0005` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0006` @@ -131,7 +131,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0006` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0007` @@ -147,7 +147,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000`.`student_0007` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0008` @@ -163,7 +163,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0008` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0009` @@ -179,7 +179,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0009` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0010` @@ -195,7 +195,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0010` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0011` @@ -211,7 +211,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0011` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0012` @@ -227,7 +227,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0012` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0013` @@ -243,7 +243,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0013` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0014` @@ -259,7 +259,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0014` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0015` @@ -275,7 +275,7 @@ CREATE TABLE IF NOT EXISTS `employees_0001`.`student_0015` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0016` @@ -291,7 +291,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0016` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0017` @@ -307,7 +307,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0017` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0018` @@ -323,7 +323,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0018` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0019` @@ -339,7 +339,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0019` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0020` @@ -355,7 +355,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0020` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0021` @@ -371,7 +371,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0021` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0022` @@ -387,7 +387,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0022` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0023` @@ -403,7 +403,7 @@ CREATE TABLE IF NOT EXISTS `employees_0002`.`student_0023` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0024` @@ -419,7 +419,7 @@ CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0024` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0025` @@ -435,7 +435,7 @@ CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0025` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0026` @@ -451,7 +451,7 @@ CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0026` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0027` @@ -467,7 +467,7 @@ CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0027` `modified_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), - KEY `nickname` (`nickname`) + KEY `nickname` (`nickname`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0003`.`student_0028` @@ -549,7 +549,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0000` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0001` ( @@ -565,7 +565,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0001` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0002` ( @@ -581,7 +581,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0002` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0003` ( @@ -597,7 +597,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0003` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0004` ( @@ -613,7 +613,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0004` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0005` ( @@ -629,7 +629,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0005` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0006` ( @@ -645,7 +645,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0006` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0007` ( @@ -661,7 +661,7 @@ CREATE TABLE IF NOT EXISTS `employees_0000_r`.`student_0007` PRIMARY KEY (`id`), UNIQUE KEY `uk_uid` (`uid`), KEY `nickname` (`nickname`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO employees_0000.student_0001(id,uid,name,score,nickname,gender,birth_year,created_at,modified_at) VALUES (1, 1, 'arana', 95, 'Awesome Arana', 0, 2021, NOW(), NOW());