Skip to content

Commit 74d83d5

Browse files
Fixed handling of GENERATED fields
1 parent e28917c commit 74d83d5

File tree

9 files changed

+1559
-34
lines changed

9 files changed

+1559
-34
lines changed

dbutils/dbutils.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dbutils
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"net/url"
7+
8+
_ "github.com/go-sql-driver/mysql"
9+
"github.com/pkg/errors"
10+
)
11+
12+
/*
13+
EXPLAIN SELECT COUNT(*) FROM sakila.actor:
14+
+----+-------------+-------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
15+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
16+
+----+-------------+-------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
17+
| 1 | SIMPLE | actor | NULL | index | NULL | idx_actor_last_name | 137 | NULL | 200 | 100.00 | Using index |
18+
+----+-------------+-------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
19+
*/
20+
type ExplainRow struct {
21+
ID int
22+
SelectType string
23+
Table string
24+
Partitions sql.NullString
25+
Type string
26+
PossibleKeys sql.NullString
27+
Key sql.NullString
28+
KeyLen sql.NullInt64
29+
Ref sql.NullString
30+
Rows int64
31+
Filtered float64
32+
Extra sql.NullString
33+
}
34+
35+
func GetApproxRowsCount(conn *sql.DB, schema, table string) (int64, error) {
36+
query := fmt.Sprintf("EXPLAIN SELECT COUNT(*) FROM `%s`.`%s`",
37+
url.QueryEscape(schema), url.QueryEscape(table))
38+
var exp ExplainRow
39+
err := conn.QueryRow(query).Scan(&exp.ID, &exp.SelectType, &exp.Table, &exp.Partitions, &exp.Type,
40+
&exp.PossibleKeys, &exp.Key, &exp.KeyLen, &exp.Ref, &exp.Rows, &exp.Filtered, &exp.Extra)
41+
if err != nil {
42+
return 0, err
43+
}
44+
return exp.Rows, nil
45+
}
46+
47+
// Explain will run MySQL EXPLAIN for a given query
48+
// If DB connection doesn't have a default schema, the query must have a fully qualified schema.table.
49+
func Explain(conn *sql.DB, query string) ([]ExplainRow, error) {
50+
query = "EXPLAIN " + query
51+
explain := []ExplainRow{}
52+
53+
rows, err := conn.Query(query)
54+
if err != nil {
55+
return nil, errors.Wrap(err, "cannot run explain")
56+
}
57+
for rows.Next() {
58+
var exp ExplainRow
59+
err := rows.Scan(&exp.ID, &exp.SelectType, &exp.Table, &exp.Partitions, &exp.Type,
60+
&exp.PossibleKeys, &exp.Key, &exp.KeyLen, &exp.Ref, &exp.Rows, &exp.Filtered, &exp.Extra)
61+
if err != nil {
62+
return nil, errors.Wrap(err, "error reading explain rows")
63+
}
64+
explain = append(explain, exp)
65+
}
66+
return explain, nil
67+
}

docker-compose.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: '3'
2+
services:
3+
mysql:
4+
image: ${MYSQL_IMAGE:-mysql:5.7}
5+
ports:
6+
- ${MYSQL_HOST:-127.0.0.1}:${MYSQL_PORT:-3306}:3306
7+
environment:
8+
- MYSQL_ALLOW_EMPTY_PASSWORD=yes
9+
# MariaDB >= 10.0.12 doesn't enable Performance Schema by default so we need to do it manually
10+
# https://mariadb.com/kb/en/mariadb/performance-schema-overview/#activating-the-performance-schema
11+
command: --performance-schema --secure-file-priv=""
12+
volumes:
13+
- ./testdata/schema/:/docker-entrypoint-initdb.d/:rw

internal/getters/getters.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ func (r *RandomTime) Value() interface{} {
208208
h := rand.Int63n(24)
209209
m := rand.Int63n(60)
210210
s := rand.Int63n(60)
211-
return fmt.Sprintf("%02d:%02d:%02d", h, m, s)
211+
return fmt.Sprintf("'%02d:%02d:%02d'", h, m, s)
212212
}
213213

214214
func (r *RandomTime) String() string {

main.go

+12
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ func makeValueFuncs(conn *sql.DB, fields []tableparser.Field) insertValues {
273273
strings.Contains(field.Extra, "auto_increment") {
274274
continue
275275
}
276+
277+
if field.GenerationExpression != "" {
278+
continue
279+
}
280+
276281
if field.Constraint != nil {
277282
samples, err := getSamples(conn, field.Constraint.ReferencedTableSchema,
278283
field.Constraint.ReferencedTableName,
@@ -285,6 +290,7 @@ func makeValueFuncs(conn *sql.DB, fields []tableparser.Field) insertValues {
285290
values = append(values, getters.NewRandomSample(field.ColumnName, samples, field.IsNullable))
286291
continue
287292
}
293+
288294
maxValue := maxValues["bigint"]
289295
if m, ok := maxValues[field.DataType]; ok {
290296
maxValue = m
@@ -326,10 +332,16 @@ func getFieldNames(fields []tableparser.Field) []string {
326332
if !isSupportedType(field.DataType) {
327333
continue
328334
}
335+
329336
if !field.IsNullable && field.ColumnKey == "PRI" &&
330337
strings.Contains(field.Extra, "auto_increment") {
331338
continue
332339
}
340+
341+
if field.GenerationExpression != "" {
342+
continue
343+
}
344+
333345
fieldNames = append(fieldNames, backticks(field.ColumnName))
334346
}
335347
return fieldNames

tableparser/tableparser.go

+38-31
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,20 @@ type Table struct {
1313
Schema string
1414
Name string
1515
Fields []Field
16-
Indexes []Index
16+
Indexes map[string]Index
1717
Constraints []Constraint
1818
Triggers []Trigger
1919
//
2020
conn *sql.DB
2121
}
2222

2323
type Index struct {
24+
Name string
25+
Unique bool
26+
Fields []string
27+
}
28+
29+
type IndexField struct {
2430
NonUnique bool
2531
KeyName string
2632
SeqInIndex int
@@ -119,28 +125,7 @@ func (t *Table) parse() error {
119125
// | | +-------- extra info (unsigned, etc)
120126
// | | |
121127
re := regexp.MustCompile("^(.*?)(?:\\((.*?)\\)(.*))?$")
122-
query := "SELECT `TABLE_CATALOG`," +
123-
"`TABLE_SCHEMA`," +
124-
"`TABLE_NAME`," +
125-
"`COLUMN_NAME`," +
126-
"`ORDINAL_POSITION`," +
127-
"`COLUMN_DEFAULT`," +
128-
"`IS_NULLABLE`," +
129-
"`DATA_TYPE`," +
130-
"`CHARACTER_MAXIMUM_LENGTH`," +
131-
"`CHARACTER_OCTET_LENGTH`," +
132-
"`NUMERIC_PRECISION`," +
133-
"`NUMERIC_SCALE`," +
134-
"`DATETIME_PRECISION`," +
135-
"`CHARACTER_SET_NAME`," +
136-
"`COLLATION_NAME`," +
137-
"`COLUMN_TYPE`," +
138-
"`COLUMN_KEY`," +
139-
"`EXTRA`," +
140-
"`PRIVILEGES`," +
141-
"`COLUMN_COMMENT`," +
142-
"`GENERATION_EXPRESSION`" +
143-
" FROM `information_schema`.`COLUMNS`" +
128+
query := "SELECT * FROM `information_schema`.`COLUMNS`" +
144129
fmt.Sprintf(" WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s'", t.Schema, t.Name)
145130

146131
constraints := constraintsAsMap(t.Constraints)
@@ -154,7 +139,8 @@ func (t *Table) parse() error {
154139
for rows.Next() {
155140
var f Field
156141
var allowNull string
157-
err := rows.Scan(&f.TableCatalog,
142+
fields := []interface{}{
143+
&f.TableCatalog,
158144
&f.TableSchema,
159145
&f.TableName,
160146
&f.ColumnName,
@@ -174,10 +160,13 @@ func (t *Table) parse() error {
174160
&f.Extra,
175161
&f.Privileges,
176162
&f.ColumnComment,
177-
&f.GenerationExpression,
178-
)
163+
}
164+
165+
if cols, err := rows.Columns(); err == nil && len(cols) > 20 { //&& cols[20] == "GENERATION_EXPRESSION" {
166+
fields = append(fields, &f.GenerationExpression)
167+
}
168+
err := rows.Scan(fields...)
179169
if err != nil {
180-
fmt.Println(err)
181170
continue
182171
}
183172

@@ -207,26 +196,44 @@ func (t *Table) parse() error {
207196
return nil
208197
}
209198

210-
func getIndexes(db *sql.DB, schema, tableName string) ([]Index, error) {
199+
func (t *Table) FieldNames() []string {
200+
fields := []string{}
201+
for _, field := range t.Fields {
202+
fields = append(fields, field.ColumnName)
203+
}
204+
return fields
205+
}
206+
207+
func getIndexes(db *sql.DB, schema, tableName string) (map[string]Index, error) {
211208
query := fmt.Sprintf("SHOW INDEXES FROM `%s`.`%s`", schema, tableName)
212209
rows, err := db.Query(query)
213210
if err != nil {
214211
return nil, err
215212
}
216213
defer rows.Close()
217214

218-
indexes := []Index{}
215+
indexes := make(map[string]Index)
219216

220217
for rows.Next() {
221-
var i Index
218+
var i IndexField
222219
var table string
223220
err := rows.Scan(&table, &i.NonUnique, &i.KeyName, &i.SeqInIndex,
224221
&i.ColumnName, &i.Collation, &i.Cardinality, &i.SubPart,
225222
&i.Packed, &i.Null, &i.IndexType, &i.Comment, &i.IndexComment)
226223
if err != nil {
227224
return nil, fmt.Errorf("cannot read constraints: %s", err)
228225
}
229-
indexes = append(indexes, i)
226+
if index, ok := indexes[i.KeyName]; !ok {
227+
indexes[i.KeyName] = Index{
228+
Name: i.KeyName,
229+
Unique: !i.NonUnique,
230+
Fields: []string{i.ColumnName},
231+
}
232+
233+
} else {
234+
index.Fields = append(index.Fields, i.ColumnName)
235+
index.Unique = index.Unique || !i.NonUnique
236+
}
230237
}
231238

232239
return indexes, nil

tableparser/tableparser_test.go

+33-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@ import (
44
"reflect"
55
"testing"
66

7-
tu "github.com/Percona-Lab/random_data_load/internal/testutils"
7+
tu "github.com/Percona-Lab/random_data_load/testutils"
88
_ "github.com/go-sql-driver/mysql"
9+
version "github.com/hashicorp/go-version"
910
)
1011

11-
func TestParse(t *testing.T) {
12+
func TestParse56(t *testing.T) {
1213
db := tu.GetMySQLConnection(t)
14+
v := tu.GetVersion(t, db)
15+
v56, _ := version.NewVersion("5.6")
16+
17+
if v.GreaterThan(v56) {
18+
t.Skipf("This test runs under MySQL < 5.7 and version is %s", v.String())
19+
}
1320

1421
table, err := NewTable(db, "sakila", "film")
1522
if err != nil {
@@ -23,6 +30,30 @@ func TestParse(t *testing.T) {
2330
}
2431
}
2532

33+
func TestParse57(t *testing.T) {
34+
db := tu.GetMySQLConnection(t)
35+
v := tu.GetVersion(t, db)
36+
v57, _ := version.NewVersion("5.7")
37+
38+
if v.LessThan(v57) {
39+
t.Skipf("This test runs under MySQL 5.7+ and version is %s", v.String())
40+
}
41+
42+
table, err := NewTable(db, "sakila", "film")
43+
if err != nil {
44+
t.Error(err)
45+
}
46+
if tu.UpdateSamples() {
47+
tu.WriteJson(t, "table002.json", table)
48+
}
49+
var want *Table
50+
tu.LoadJson(t, "table002.json", &want)
51+
52+
if !reflect.DeepEqual(table, want) {
53+
t.Error("Table film was not correctly parsed")
54+
}
55+
}
56+
2657
func TestGetIndexes(t *testing.T) {
2758
db := tu.GetMySQLConnection(t)
2859
want := []Index{}

0 commit comments

Comments
 (0)