Skip to content

Commit

Permalink
Merge branch 'main' into 297-ambiguous-reference
Browse files Browse the repository at this point in the history
  • Loading branch information
TianyuZhang1214 authored Dec 30, 2024
2 parents 2704cda + 062442f commit a5a91b3
Show file tree
Hide file tree
Showing 6 changed files with 301 additions and 64 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/bats-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
pip3 install "sqlglot[rs]" pyarrow pandas
curl -LJO https://github.com/duckdb/duckdb/releases/download/v1.1.3/duckdb_cli-linux-amd64.zip
curl -LJO https://github.com/duckdb/duckdb/releases/latest/download/duckdb_cli-linux-amd64.zip
unzip duckdb_cli-linux-amd64.zip
chmod +x duckdb
sudo mv duckdb /usr/local/bin
Expand Down
99 changes: 99 additions & 0 deletions .github/workflows/mysql-copy-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: MySQL Copy Instance Test

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:
copy-instance-test:
runs-on: ubuntu-latest
services:
source:
image: mysql:lts
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 13306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.13'

- name: Install dependencies
run: |
go get .
pip3 install "sqlglot[rs]"
curl -LJO https://dev.mysql.com/get/Downloads/MySQL-Shell/mysql-shell_9.1.0-1debian12_amd64.deb
sudo apt-get install -y ./mysql-shell_9.1.0-1debian12_amd64.deb
- name: Setup test data in source MySQL
run: |
mysqlsh -hlocalhost -P13306 -uroot -proot --sql -e "
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100)
);
INSERT INTO users (name) VALUES ('test1'), ('test2'), ('test3');
-- Make a gap in the id sequence
INSERT INTO users VALUES (100, 'test100');
INSERT INTO users (name) VALUES ('test101');
-- A table with non-default starting auto_increment value
CREATE TABLE items (
id INT AUTO_INCREMENT PRIMARY KEY,
v BIGINT check (v > 0),
name VARCHAR(100)
) AUTO_INCREMENT=1000;
INSERT INTO items (v, name) VALUES (1, 'item1'), (2, 'item2'), (3, 'item3');
"
- name: Build and start MyDuck Server
run: |
go build -v
./myduckserver &
sleep 5
- name: Run copy-instance test
run: |
# Set local_infile to true to allow loading data from files
mysqlsh -uroot --no-password --sql -e "SET GLOBAL local_infile = 1;"
# Copy the data from source MySQL to MyDuck
mysqlsh -hlocalhost -P13306 -uroot -proot \
-- util copy-instance "mysql://root:@127.0.0.1:3306" \
--users false --ignore-version true
# Verify the data was copied
for table in users items; do
mysqlsh -hlocalhost -P13306 -uroot -proot --sql -e "
SELECT * FROM testdb.$table ORDER BY id;
" | tee source_data_$table.tsv
mysqlsh -uroot --no-password --sql -e "
SELECT * FROM testdb.$table ORDER BY id;
" | tee copied_data_$table.tsv
diff source_data_$table.tsv copied_data_$table.tsv
done
27 changes: 20 additions & 7 deletions backend/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,12 @@ func (b *DuckBuilder) Build(ctx *sql.Context, root sql.Node, r sql.Row) (sql.Row
case *plan.InsertInto:
insert := n.(*plan.InsertInto)

// For AUTO_INCREMENT column, we fallback to the framework if the column is specified.
if dst, err := plan.GetInsertable(insert.Destination); err == nil && dst.Schema().HasAutoIncrement() {
if len(insert.ColumnNames) == 0 || len(insert.ColumnNames) == len(dst.Schema()) {
return b.base.Build(ctx, root, r)
}
}

// The handling of auto_increment reset and check constraints is not supported by DuckDB.
// We need to fallback to the framework for these cases.
// But we want to rewrite LOAD DATA to be handled by DuckDB,
// as it is a common way to import data into the database.
// Therefore, we ignoring auto_increment and check constraints for LOAD DATA.
// So rewriting LOAD DATA is done eagerly here.
src := insert.Source
if proj, ok := src.(*plan.Project); ok {
src = proj.Child
Expand All @@ -97,6 +96,20 @@ func (b *DuckBuilder) Build(ctx *sql.Context, root sql.Node, r sql.Row) (sql.Row
}
return b.base.Build(ctx, root, r)
}

if dst, err := plan.GetInsertable(insert.Destination); err == nil {
// For AUTO_INCREMENT column, we fallback to the framework if the column is specified.
// if dst.Schema().HasAutoIncrement() && (0 == len(insert.ColumnNames) || len(insert.ColumnNames) == len(dst.Schema())) {
if dst.Schema().HasAutoIncrement() {
return b.base.Build(ctx, root, r)
}
// For table with check constraints, we fallback to the framework.
if ct, ok := dst.(sql.CheckTable); ok {
if checks, err := ct.GetChecks(ctx); err == nil && len(checks) > 0 {
return b.base.Build(ctx, root, r)
}
}
}
}

// Fallback to the base builder if the plan contains system/user variables or is not a pure data query.
Expand Down
16 changes: 13 additions & 3 deletions catalog/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,29 @@ func (d *Database) GetTableInsensitive(ctx *sql.Context, tblName string) (sql.Ta
func (d *Database) tablesInsensitive(ctx *sql.Context, pattern string) ([]*Table, error) {
tables, err := d.findTables(ctx, pattern)
if err != nil {
ctx.GetLogger().WithFields(logrus.Fields{
"catalog": d.catalog,
"database": d.name,
"pattern": pattern,
}).WithError(err).Error("Failed to find tables")
return nil, err
}
for _, t := range tables {
if err := t.withSchema(ctx); err != nil {
ctx.GetLogger().WithFields(logrus.Fields{
"catalog": d.catalog,
"database": d.name,
"pattern": pattern,
"table": t.Name(),
}).WithError(err).Error("Failed to get table schema")
return nil, err
}
}
return tables, nil
}

func (d *Database) findTables(ctx *sql.Context, pattern string) ([]*Table, error) {
rows, err := adapter.QueryCatalog(ctx, "SELECT DISTINCT table_name, comment FROM duckdb_tables() where (database_name = ? and schema_name = ? and table_name ILIKE ?) or (database_name = 'temp' and schema_name = 'main' and table_name ILIKE ?)", d.catalog, d.name, pattern, pattern)
rows, err := adapter.QueryCatalog(ctx, "SELECT DISTINCT table_name, comment FROM duckdb_tables() WHERE (database_name = ? AND schema_name = ? AND table_name ILIKE ?) OR (temporary IS TRUE AND table_name ILIKE ?)", d.catalog, d.name, pattern, pattern)
if err != nil {
return nil, ErrDuckDB.New(err)
}
Expand Down Expand Up @@ -113,7 +124,6 @@ func (d *Database) Name() string {
}

func (d *Database) createAllTable(ctx *sql.Context, name string, schema sql.PrimaryKeySchema, collation sql.CollationID, comment string, temporary bool) error {

var columns []string
var columnCommentSQLs []string
var fullTableName string
Expand Down Expand Up @@ -219,7 +229,7 @@ func (d *Database) createAllTable(ctx *sql.Context, name string, schema sql.Prim
b.WriteString(")")

// Add comment to the table
info := ExtraTableInfo{schema.PkOrdinals, withoutIndex, fullSequenceName}
info := ExtraTableInfo{schema.PkOrdinals, withoutIndex, fullSequenceName, nil}
b.WriteString(fmt.Sprintf(
"; COMMENT ON TABLE %s IS '%s'",
fullTableName,
Expand Down
Loading

0 comments on commit a5a91b3

Please sign in to comment.