2016-06-18 19:12:07 +00:00
|
|
|
/*
|
2022-07-18 16:37:18 +00:00
|
|
|
Copyright 2022 GitHub Inc.
|
2016-06-18 19:12:07 +00:00
|
|
|
See https://github.com/github/gh-ost/blob/master/LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
package sql
|
|
|
|
|
|
|
|
import (
|
2016-11-29 14:44:38 +00:00
|
|
|
"reflect"
|
2016-06-18 19:12:07 +00:00
|
|
|
"testing"
|
|
|
|
|
2021-06-24 18:19:37 +00:00
|
|
|
"github.com/openark/golib/log"
|
|
|
|
test "github.com/openark/golib/tests"
|
2016-06-18 19:12:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
log.SetLevel(log.ERROR)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseAlterStatement(t *testing.T) {
|
|
|
|
statement := "add column t int, engine=innodb"
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2016-06-18 19:12:07 +00:00
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, statement)
|
2016-06-18 19:12:07 +00:00
|
|
|
test.S(t).ExpectFalse(parser.HasNonTrivialRenames())
|
2021-05-14 13:32:56 +00:00
|
|
|
test.S(t).ExpectFalse(parser.IsAutoIncrementDefined())
|
2016-06-18 19:12:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseAlterStatementTrivialRename(t *testing.T) {
|
|
|
|
statement := "add column t int, change ts ts timestamp, engine=innodb"
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2016-06-18 19:12:07 +00:00
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, statement)
|
2016-06-18 19:12:07 +00:00
|
|
|
test.S(t).ExpectFalse(parser.HasNonTrivialRenames())
|
2021-05-14 13:32:56 +00:00
|
|
|
test.S(t).ExpectFalse(parser.IsAutoIncrementDefined())
|
2016-06-18 19:12:07 +00:00
|
|
|
test.S(t).ExpectEquals(len(parser.columnRenameMap), 1)
|
|
|
|
test.S(t).ExpectEquals(parser.columnRenameMap["ts"], "ts")
|
|
|
|
}
|
|
|
|
|
2021-05-14 13:32:56 +00:00
|
|
|
func TestParseAlterStatementWithAutoIncrement(t *testing.T) {
|
|
|
|
statements := []string{
|
|
|
|
"auto_increment=7",
|
|
|
|
"auto_increment = 7",
|
|
|
|
"AUTO_INCREMENT = 71",
|
|
|
|
"add column t int, change ts ts timestamp, auto_increment=7 engine=innodb",
|
|
|
|
"add column t int, change ts ts timestamp, auto_increment =7 engine=innodb",
|
|
|
|
"add column t int, change ts ts timestamp, AUTO_INCREMENT = 7 engine=innodb",
|
|
|
|
"add column t int, change ts ts timestamp, engine=innodb auto_increment=73425",
|
|
|
|
}
|
|
|
|
for _, statement := range statements {
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, statement)
|
|
|
|
test.S(t).ExpectTrue(parser.IsAutoIncrementDefined())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-18 19:12:07 +00:00
|
|
|
func TestParseAlterStatementTrivialRenames(t *testing.T) {
|
|
|
|
statement := "add column t int, change ts ts timestamp, CHANGE f `f` float, engine=innodb"
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2016-06-18 19:12:07 +00:00
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, statement)
|
2016-06-18 19:12:07 +00:00
|
|
|
test.S(t).ExpectFalse(parser.HasNonTrivialRenames())
|
2021-05-14 13:32:56 +00:00
|
|
|
test.S(t).ExpectFalse(parser.IsAutoIncrementDefined())
|
2016-06-18 19:12:07 +00:00
|
|
|
test.S(t).ExpectEquals(len(parser.columnRenameMap), 2)
|
|
|
|
test.S(t).ExpectEquals(parser.columnRenameMap["ts"], "ts")
|
|
|
|
test.S(t).ExpectEquals(parser.columnRenameMap["f"], "f")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseAlterStatementNonTrivial(t *testing.T) {
|
|
|
|
statements := []string{
|
|
|
|
`add column b bigint, change f fl float, change i count int, engine=innodb`,
|
|
|
|
"add column b bigint, change column `f` fl float, change `i` `count` int, engine=innodb",
|
|
|
|
"add column b bigint, change column `f` fl float, change `i` `count` int, change ts ts timestamp, engine=innodb",
|
|
|
|
`change
|
|
|
|
f fl float,
|
|
|
|
CHANGE COLUMN i
|
|
|
|
count int, engine=innodb`,
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, statement := range statements {
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2016-06-18 19:12:07 +00:00
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
2021-05-14 13:32:56 +00:00
|
|
|
test.S(t).ExpectFalse(parser.IsAutoIncrementDefined())
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, statement)
|
2016-06-18 19:12:07 +00:00
|
|
|
renames := parser.GetNonTrivialRenames()
|
|
|
|
test.S(t).ExpectEquals(len(renames), 2)
|
|
|
|
test.S(t).ExpectEquals(renames["i"], "count")
|
|
|
|
test.S(t).ExpectEquals(renames["f"], "fl")
|
|
|
|
}
|
|
|
|
}
|
2016-11-29 14:44:38 +00:00
|
|
|
|
|
|
|
func TestTokenizeAlterStatement(t *testing.T) {
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2016-11-29 14:44:38 +00:00
|
|
|
{
|
|
|
|
alterStatement := "add column t int"
|
|
|
|
tokens, _ := parser.tokenizeAlterStatement(alterStatement)
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(tokens, []string{"add column t int"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
alterStatement := "add column t int, change column i int"
|
|
|
|
tokens, _ := parser.tokenizeAlterStatement(alterStatement)
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(tokens, []string{"add column t int", "change column i int"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
alterStatement := "add column t int, change column i int 'some comment'"
|
|
|
|
tokens, _ := parser.tokenizeAlterStatement(alterStatement)
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(tokens, []string{"add column t int", "change column i int 'some comment'"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
alterStatement := "add column t int, change column i int 'some comment, with comma'"
|
|
|
|
tokens, _ := parser.tokenizeAlterStatement(alterStatement)
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(tokens, []string{"add column t int", "change column i int 'some comment, with comma'"}))
|
|
|
|
}
|
2016-11-29 14:47:39 +00:00
|
|
|
{
|
|
|
|
alterStatement := "add column t int, add column d decimal(10,2)"
|
|
|
|
tokens, _ := parser.tokenizeAlterStatement(alterStatement)
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(tokens, []string{"add column t int", "add column d decimal(10,2)"}))
|
|
|
|
}
|
2016-11-29 14:44:38 +00:00
|
|
|
{
|
|
|
|
alterStatement := "add column t int, add column e enum('a','b','c')"
|
|
|
|
tokens, _ := parser.tokenizeAlterStatement(alterStatement)
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(tokens, []string{"add column t int", "add column e enum('a','b','c')"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
alterStatement := "add column t int(11), add column e enum('a','b','c')"
|
|
|
|
tokens, _ := parser.tokenizeAlterStatement(alterStatement)
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(tokens, []string{"add column t int(11)", "add column e enum('a','b','c')"}))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-29 14:47:39 +00:00
|
|
|
func TestSanitizeQuotesFromAlterStatement(t *testing.T) {
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2016-11-29 14:44:38 +00:00
|
|
|
{
|
|
|
|
alterStatement := "add column e enum('a','b','c')"
|
2016-11-29 14:47:39 +00:00
|
|
|
strippedStatement := parser.sanitizeQuotesFromAlterStatement(alterStatement)
|
2016-11-29 14:44:38 +00:00
|
|
|
test.S(t).ExpectEquals(strippedStatement, "add column e enum('','','')")
|
|
|
|
}
|
|
|
|
{
|
|
|
|
alterStatement := "change column i int 'some comment, with comma'"
|
2016-11-29 14:47:39 +00:00
|
|
|
strippedStatement := parser.sanitizeQuotesFromAlterStatement(alterStatement)
|
2016-11-29 14:44:38 +00:00
|
|
|
test.S(t).ExpectEquals(strippedStatement, "change column i int ''")
|
|
|
|
}
|
|
|
|
}
|
2017-04-23 05:23:56 +00:00
|
|
|
|
|
|
|
func TestParseAlterStatementDroppedColumns(t *testing.T) {
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2017-04-23 05:23:56 +00:00
|
|
|
statement := "drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(len(parser.droppedColumns), 1)
|
|
|
|
test.S(t).ExpectTrue(parser.droppedColumns["b"])
|
|
|
|
}
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2017-04-23 05:23:56 +00:00
|
|
|
statement := "drop column b, drop key c_idx, drop column `d`"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, statement)
|
2017-04-23 05:23:56 +00:00
|
|
|
test.S(t).ExpectEquals(len(parser.droppedColumns), 2)
|
|
|
|
test.S(t).ExpectTrue(parser.droppedColumns["b"])
|
|
|
|
test.S(t).ExpectTrue(parser.droppedColumns["d"])
|
|
|
|
}
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2017-04-23 05:23:56 +00:00
|
|
|
statement := "drop column b, drop key c_idx, drop column `d`, drop `e`, drop primary key, drop foreign key fk_1"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(len(parser.droppedColumns), 3)
|
|
|
|
test.S(t).ExpectTrue(parser.droppedColumns["b"])
|
|
|
|
test.S(t).ExpectTrue(parser.droppedColumns["d"])
|
|
|
|
test.S(t).ExpectTrue(parser.droppedColumns["e"])
|
|
|
|
}
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2017-04-23 05:23:56 +00:00
|
|
|
statement := "drop column b, drop bad statement, add column i int"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(len(parser.droppedColumns), 1)
|
|
|
|
test.S(t).ExpectTrue(parser.droppedColumns["b"])
|
|
|
|
}
|
|
|
|
}
|
2018-05-06 08:19:03 +00:00
|
|
|
|
|
|
|
func TestParseAlterStatementRenameTable(t *testing.T) {
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2018-05-06 08:19:03 +00:00
|
|
|
statement := "drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectFalse(parser.isRenameTable)
|
|
|
|
}
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2018-05-06 08:19:03 +00:00
|
|
|
statement := "rename as something_else"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectTrue(parser.isRenameTable)
|
|
|
|
}
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2018-05-06 08:19:03 +00:00
|
|
|
statement := "drop column b, rename as something_else"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, statement)
|
2018-05-06 08:19:03 +00:00
|
|
|
test.S(t).ExpectTrue(parser.isRenameTable)
|
|
|
|
}
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2018-05-06 08:19:03 +00:00
|
|
|
statement := "engine=innodb rename as something_else"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectTrue(parser.isRenameTable)
|
|
|
|
}
|
|
|
|
{
|
2020-07-22 09:33:02 +00:00
|
|
|
parser := NewAlterTableParser()
|
2018-05-06 08:19:03 +00:00
|
|
|
statement := "rename as something_else, engine=innodb"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectTrue(parser.isRenameTable)
|
|
|
|
}
|
|
|
|
}
|
2020-07-22 09:33:02 +00:00
|
|
|
|
|
|
|
func TestParseAlterStatementExplicitTable(t *testing.T) {
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table tbl drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table `tbl` drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table `scm with spaces`.`tbl` drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "scm with spaces")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table `scm`.`tbl with spaces` drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "scm")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl with spaces")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table `scm`.tbl drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "scm")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table scm.`tbl` drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "scm")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table scm.tbl drop column b"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "scm")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl")
|
2020-07-29 12:06:13 +00:00
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b")
|
2020-07-22 09:33:02 +00:00
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b"}))
|
|
|
|
}
|
2020-07-29 12:06:13 +00:00
|
|
|
{
|
|
|
|
parser := NewAlterTableParser()
|
|
|
|
statement := "alter table scm.tbl drop column b, add index idx(i)"
|
|
|
|
err := parser.ParseAlterStatement(statement)
|
|
|
|
test.S(t).ExpectNil(err)
|
|
|
|
test.S(t).ExpectEquals(parser.explicitSchema, "scm")
|
|
|
|
test.S(t).ExpectEquals(parser.explicitTable, "tbl")
|
|
|
|
test.S(t).ExpectEquals(parser.alterStatementOptions, "drop column b, add index idx(i)")
|
|
|
|
test.S(t).ExpectTrue(reflect.DeepEqual(parser.alterTokens, []string{"drop column b", "add index idx(i)"}))
|
|
|
|
}
|
2020-07-22 09:33:02 +00:00
|
|
|
}
|
2021-06-10 15:17:49 +00:00
|
|
|
|
|
|
|
func TestParseEnumValues(t *testing.T) {
|
|
|
|
{
|
|
|
|
s := "enum('red','green','blue','orange')"
|
|
|
|
values := ParseEnumValues(s)
|
|
|
|
test.S(t).ExpectEquals(values, "'red','green','blue','orange'")
|
|
|
|
}
|
|
|
|
{
|
|
|
|
s := "('red','green','blue','orange')"
|
|
|
|
values := ParseEnumValues(s)
|
|
|
|
test.S(t).ExpectEquals(values, "('red','green','blue','orange')")
|
|
|
|
}
|
|
|
|
{
|
|
|
|
s := "zzz"
|
|
|
|
values := ParseEnumValues(s)
|
|
|
|
test.S(t).ExpectEquals(values, "zzz")
|
|
|
|
}
|
|
|
|
}
|