sql.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. package gofuzzheaders
  2. import (
  3. "fmt"
  4. "strings"
  5. )
  6. // returns a keyword by index
  7. func getKeyword(f *ConsumeFuzzer) (string, error) {
  8. index, err := f.GetInt()
  9. if err != nil {
  10. return keywords[0], err
  11. }
  12. for i, k := range keywords {
  13. if i == index {
  14. return k, nil
  15. }
  16. }
  17. return keywords[0], fmt.Errorf("could not get a kw")
  18. }
  19. // Simple utility function to check if a string
  20. // slice contains a string.
  21. func containsString(s []string, e string) bool {
  22. for _, a := range s {
  23. if a == e {
  24. return true
  25. }
  26. }
  27. return false
  28. }
  29. // These keywords are used specifically for fuzzing Vitess
  30. var keywords = []string{
  31. "accessible", "action", "add", "after", "against", "algorithm",
  32. "all", "alter", "always", "analyze", "and", "as", "asc", "asensitive",
  33. "auto_increment", "avg_row_length", "before", "begin", "between",
  34. "bigint", "binary", "_binary", "_utf8mb4", "_utf8", "_latin1", "bit",
  35. "blob", "bool", "boolean", "both", "by", "call", "cancel", "cascade",
  36. "cascaded", "case", "cast", "channel", "change", "char", "character",
  37. "charset", "check", "checksum", "coalesce", "code", "collate", "collation",
  38. "column", "columns", "comment", "committed", "commit", "compact", "complete",
  39. "compressed", "compression", "condition", "connection", "constraint", "continue",
  40. "convert", "copy", "cume_dist", "substr", "substring", "create", "cross",
  41. "csv", "current_date", "current_time", "current_timestamp", "current_user",
  42. "cursor", "data", "database", "databases", "day", "day_hour", "day_microsecond",
  43. "day_minute", "day_second", "date", "datetime", "dec", "decimal", "declare",
  44. "default", "definer", "delay_key_write", "delayed", "delete", "dense_rank",
  45. "desc", "describe", "deterministic", "directory", "disable", "discard",
  46. "disk", "distinct", "distinctrow", "div", "double", "do", "drop", "dumpfile",
  47. "duplicate", "dynamic", "each", "else", "elseif", "empty", "enable",
  48. "enclosed", "encryption", "end", "enforced", "engine", "engines", "enum",
  49. "error", "escape", "escaped", "event", "exchange", "exclusive", "exists",
  50. "exit", "explain", "expansion", "export", "extended", "extract", "false",
  51. "fetch", "fields", "first", "first_value", "fixed", "float", "float4",
  52. "float8", "flush", "for", "force", "foreign", "format", "from", "full",
  53. "fulltext", "function", "general", "generated", "geometry", "geometrycollection",
  54. "get", "global", "gtid_executed", "grant", "group", "grouping", "groups",
  55. "group_concat", "having", "header", "high_priority", "hosts", "hour", "hour_microsecond",
  56. "hour_minute", "hour_second", "if", "ignore", "import", "in", "index", "indexes",
  57. "infile", "inout", "inner", "inplace", "insensitive", "insert", "insert_method",
  58. "int", "int1", "int2", "int3", "int4", "int8", "integer", "interval",
  59. "into", "io_after_gtids", "is", "isolation", "iterate", "invoker", "join",
  60. "json", "json_table", "key", "keys", "keyspaces", "key_block_size", "kill", "lag",
  61. "language", "last", "last_value", "last_insert_id", "lateral", "lead", "leading",
  62. "leave", "left", "less", "level", "like", "limit", "linear", "lines",
  63. "linestring", "load", "local", "localtime", "localtimestamp", "lock", "logs",
  64. "long", "longblob", "longtext", "loop", "low_priority", "manifest",
  65. "master_bind", "match", "max_rows", "maxvalue", "mediumblob", "mediumint",
  66. "mediumtext", "memory", "merge", "microsecond", "middleint", "min_rows", "minute",
  67. "minute_microsecond", "minute_second", "mod", "mode", "modify", "modifies",
  68. "multilinestring", "multipoint", "multipolygon", "month", "name",
  69. "names", "natural", "nchar", "next", "no", "none", "not", "no_write_to_binlog",
  70. "nth_value", "ntile", "null", "numeric", "of", "off", "offset", "on",
  71. "only", "open", "optimize", "optimizer_costs", "option", "optionally",
  72. "or", "order", "out", "outer", "outfile", "over", "overwrite", "pack_keys",
  73. "parser", "partition", "partitioning", "password", "percent_rank", "plugins",
  74. "point", "polygon", "precision", "primary", "privileges", "processlist",
  75. "procedure", "query", "quarter", "range", "rank", "read", "reads", "read_write",
  76. "real", "rebuild", "recursive", "redundant", "references", "regexp", "relay",
  77. "release", "remove", "rename", "reorganize", "repair", "repeat", "repeatable",
  78. "replace", "require", "resignal", "restrict", "return", "retry", "revert",
  79. "revoke", "right", "rlike", "rollback", "row", "row_format", "row_number",
  80. "rows", "s3", "savepoint", "schema", "schemas", "second", "second_microsecond",
  81. "security", "select", "sensitive", "separator", "sequence", "serializable",
  82. "session", "set", "share", "shared", "show", "signal", "signed", "slow",
  83. "smallint", "spatial", "specific", "sql", "sqlexception", "sqlstate",
  84. "sqlwarning", "sql_big_result", "sql_cache", "sql_calc_found_rows",
  85. "sql_no_cache", "sql_small_result", "ssl", "start", "starting",
  86. "stats_auto_recalc", "stats_persistent", "stats_sample_pages", "status",
  87. "storage", "stored", "straight_join", "stream", "system", "vstream",
  88. "table", "tables", "tablespace", "temporary", "temptable", "terminated",
  89. "text", "than", "then", "time", "timestamp", "timestampadd", "timestampdiff",
  90. "tinyblob", "tinyint", "tinytext", "to", "trailing", "transaction", "tree",
  91. "traditional", "trigger", "triggers", "true", "truncate", "uncommitted",
  92. "undefined", "undo", "union", "unique", "unlock", "unsigned", "update",
  93. "upgrade", "usage", "use", "user", "user_resources", "using", "utc_date",
  94. "utc_time", "utc_timestamp", "validation", "values", "variables", "varbinary",
  95. "varchar", "varcharacter", "varying", "vgtid_executed", "virtual", "vindex",
  96. "vindexes", "view", "vitess", "vitess_keyspaces", "vitess_metadata",
  97. "vitess_migration", "vitess_migrations", "vitess_replication_status",
  98. "vitess_shards", "vitess_tablets", "vschema", "warnings", "when",
  99. "where", "while", "window", "with", "without", "work", "write", "xor",
  100. "year", "year_month", "zerofill",
  101. }
  102. // Keywords that could get an additional keyword
  103. var needCustomString = []string{
  104. "DISTINCTROW", "FROM", // Select keywords:
  105. "GROUP BY", "HAVING", "WINDOW",
  106. "FOR",
  107. "ORDER BY", "LIMIT",
  108. "INTO", "PARTITION", "AS", // Insert Keywords:
  109. "ON DUPLICATE KEY UPDATE",
  110. "WHERE", "LIMIT", // Delete keywords
  111. "INFILE", "INTO TABLE", "CHARACTER SET", // Load keywords
  112. "TERMINATED BY", "ENCLOSED BY",
  113. "ESCAPED BY", "STARTING BY",
  114. "TERMINATED BY", "STARTING BY",
  115. "IGNORE",
  116. "VALUE", "VALUES", // Replace tokens
  117. "SET", // Update tokens
  118. "ENGINE =", // Drop tokens
  119. "DEFINER =", "ON SCHEDULE", "RENAME TO", // Alter tokens
  120. "COMMENT", "DO", "INITIAL_SIZE = ", "OPTIONS",
  121. }
  122. var alterTableTokens = [][]string{
  123. {"CUSTOM_FUZZ_STRING"},
  124. {"CUSTOM_ALTTER_TABLE_OPTIONS"},
  125. {"PARTITION_OPTIONS_FOR_ALTER_TABLE"},
  126. }
  127. var alterTokens = [][]string{
  128. {
  129. "DATABASE", "SCHEMA", "DEFINER = ", "EVENT", "FUNCTION", "INSTANCE",
  130. "LOGFILE GROUP", "PROCEDURE", "SERVER",
  131. },
  132. {"CUSTOM_FUZZ_STRING"},
  133. {
  134. "ON SCHEDULE", "ON COMPLETION PRESERVE", "ON COMPLETION NOT PRESERVE",
  135. "ADD UNDOFILE", "OPTIONS",
  136. },
  137. {"RENAME TO", "INITIAL_SIZE = "},
  138. {"ENABLE", "DISABLE", "DISABLE ON SLAVE", "ENGINE"},
  139. {"COMMENT"},
  140. {"DO"},
  141. }
  142. var setTokens = [][]string{
  143. {"CHARACTER SET", "CHARSET", "CUSTOM_FUZZ_STRING", "NAMES"},
  144. {"CUSTOM_FUZZ_STRING", "DEFAULT", "="},
  145. {"CUSTOM_FUZZ_STRING"},
  146. }
  147. var dropTokens = [][]string{
  148. {"TEMPORARY", "UNDO"},
  149. {
  150. "DATABASE", "SCHEMA", "EVENT", "INDEX", "LOGFILE GROUP",
  151. "PROCEDURE", "FUNCTION", "SERVER", "SPATIAL REFERENCE SYSTEM",
  152. "TABLE", "TABLESPACE", "TRIGGER", "VIEW",
  153. },
  154. {"IF EXISTS"},
  155. {"CUSTOM_FUZZ_STRING"},
  156. {"ON", "ENGINE = ", "RESTRICT", "CASCADE"},
  157. }
  158. var renameTokens = [][]string{
  159. {"TABLE"},
  160. {"CUSTOM_FUZZ_STRING"},
  161. {"TO"},
  162. {"CUSTOM_FUZZ_STRING"},
  163. }
  164. var truncateTokens = [][]string{
  165. {"TABLE"},
  166. {"CUSTOM_FUZZ_STRING"},
  167. }
  168. var createTokens = [][]string{
  169. {"OR REPLACE", "TEMPORARY", "UNDO"}, // For create spatial reference system
  170. {
  171. "UNIQUE", "FULLTEXT", "SPATIAL", "ALGORITHM = UNDEFINED", "ALGORITHM = MERGE",
  172. "ALGORITHM = TEMPTABLE",
  173. },
  174. {
  175. "DATABASE", "SCHEMA", "EVENT", "FUNCTION", "INDEX", "LOGFILE GROUP",
  176. "PROCEDURE", "SERVER", "SPATIAL REFERENCE SYSTEM", "TABLE", "TABLESPACE",
  177. "TRIGGER", "VIEW",
  178. },
  179. {"IF NOT EXISTS"},
  180. {"CUSTOM_FUZZ_STRING"},
  181. }
  182. /*
  183. // For future use.
  184. var updateTokens = [][]string{
  185. {"LOW_PRIORITY"},
  186. {"IGNORE"},
  187. {"SET"},
  188. {"WHERE"},
  189. {"ORDER BY"},
  190. {"LIMIT"},
  191. }
  192. */
  193. var replaceTokens = [][]string{
  194. {"LOW_PRIORITY", "DELAYED"},
  195. {"INTO"},
  196. {"PARTITION"},
  197. {"CUSTOM_FUZZ_STRING"},
  198. {"VALUES", "VALUE"},
  199. }
  200. var loadTokens = [][]string{
  201. {"DATA"},
  202. {"LOW_PRIORITY", "CONCURRENT", "LOCAL"},
  203. {"INFILE"},
  204. {"REPLACE", "IGNORE"},
  205. {"INTO TABLE"},
  206. {"PARTITION"},
  207. {"CHARACTER SET"},
  208. {"FIELDS", "COLUMNS"},
  209. {"TERMINATED BY"},
  210. {"OPTIONALLY"},
  211. {"ENCLOSED BY"},
  212. {"ESCAPED BY"},
  213. {"LINES"},
  214. {"STARTING BY"},
  215. {"TERMINATED BY"},
  216. {"IGNORE"},
  217. {"LINES", "ROWS"},
  218. {"CUSTOM_FUZZ_STRING"},
  219. }
  220. // These Are everything that comes after "INSERT"
  221. var insertTokens = [][]string{
  222. {"LOW_PRIORITY", "DELAYED", "HIGH_PRIORITY", "IGNORE"},
  223. {"INTO"},
  224. {"PARTITION"},
  225. {"CUSTOM_FUZZ_STRING"},
  226. {"AS"},
  227. {"ON DUPLICATE KEY UPDATE"},
  228. }
  229. // These are everything that comes after "SELECT"
  230. var selectTokens = [][]string{
  231. {"*", "CUSTOM_FUZZ_STRING", "DISTINCTROW"},
  232. {"HIGH_PRIORITY"},
  233. {"STRAIGHT_JOIN"},
  234. {"SQL_SMALL_RESULT", "SQL_BIG_RESULT", "SQL_BUFFER_RESULT"},
  235. {"SQL_NO_CACHE", "SQL_CALC_FOUND_ROWS"},
  236. {"CUSTOM_FUZZ_STRING"},
  237. {"FROM"},
  238. {"WHERE"},
  239. {"GROUP BY"},
  240. {"HAVING"},
  241. {"WINDOW"},
  242. {"ORDER BY"},
  243. {"LIMIT"},
  244. {"CUSTOM_FUZZ_STRING"},
  245. {"FOR"},
  246. }
  247. // These are everything that comes after "DELETE"
  248. var deleteTokens = [][]string{
  249. {"LOW_PRIORITY", "QUICK", "IGNORE", "FROM", "AS"},
  250. {"PARTITION"},
  251. {"WHERE"},
  252. {"ORDER BY"},
  253. {"LIMIT"},
  254. }
  255. var alter_table_options = []string{
  256. "ADD", "COLUMN", "FIRST", "AFTER", "INDEX", "KEY", "FULLTEXT", "SPATIAL",
  257. "CONSTRAINT", "UNIQUE", "FOREIGN KEY", "CHECK", "ENFORCED", "DROP", "ALTER",
  258. "NOT", "INPLACE", "COPY", "SET", "VISIBLE", "INVISIBLE", "DEFAULT", "CHANGE",
  259. "CHARACTER SET", "COLLATE", "DISABLE", "ENABLE", "KEYS", "TABLESPACE", "LOCK",
  260. "FORCE", "MODIFY", "SHARED", "EXCLUSIVE", "NONE", "ORDER BY", "RENAME COLUMN",
  261. "AS", "=", "ASC", "DESC", "WITH", "WITHOUT", "VALIDATION", "ADD PARTITION",
  262. "DROP PARTITION", "DISCARD PARTITION", "IMPORT PARTITION", "TRUNCATE PARTITION",
  263. "COALESCE PARTITION", "REORGANIZE PARTITION", "EXCHANGE PARTITION",
  264. "ANALYZE PARTITION", "CHECK PARTITION", "OPTIMIZE PARTITION", "REBUILD PARTITION",
  265. "REPAIR PARTITION", "REMOVE PARTITIONING", "USING", "BTREE", "HASH", "COMMENT",
  266. "KEY_BLOCK_SIZE", "WITH PARSER", "AUTOEXTEND_SIZE", "AUTO_INCREMENT", "AVG_ROW_LENGTH",
  267. "CHECKSUM", "INSERT_METHOD", "ROW_FORMAT", "DYNAMIC", "FIXED", "COMPRESSED", "REDUNDANT",
  268. "COMPACT", "SECONDARY_ENGINE_ATTRIBUTE", "STATS_AUTO_RECALC", "STATS_PERSISTENT",
  269. "STATS_SAMPLE_PAGES", "ZLIB", "LZ4", "ENGINE_ATTRIBUTE", "KEY_BLOCK_SIZE", "MAX_ROWS",
  270. "MIN_ROWS", "PACK_KEYS", "PASSWORD", "COMPRESSION", "CONNECTION", "DIRECTORY",
  271. "DELAY_KEY_WRITE", "ENCRYPTION", "STORAGE", "DISK", "MEMORY", "UNION",
  272. }
  273. // Creates an 'alter table' statement. 'alter table' is an exception
  274. // in that it has its own function. The majority of statements
  275. // are created by 'createStmt()'.
  276. func createAlterTableStmt(f *ConsumeFuzzer) (string, error) {
  277. maxArgs, err := f.GetInt()
  278. if err != nil {
  279. return "", err
  280. }
  281. maxArgs = maxArgs % 30
  282. if maxArgs == 0 {
  283. return "", fmt.Errorf("could not create alter table stmt")
  284. }
  285. var stmt strings.Builder
  286. stmt.WriteString("ALTER TABLE ")
  287. for i := 0; i < maxArgs; i++ {
  288. // Calculate if we get existing token or custom string
  289. tokenType, err := f.GetInt()
  290. if err != nil {
  291. return "", err
  292. }
  293. if tokenType%4 == 1 {
  294. customString, err := f.GetString()
  295. if err != nil {
  296. return "", err
  297. }
  298. stmt.WriteString(" " + customString)
  299. } else {
  300. tokenIndex, err := f.GetInt()
  301. if err != nil {
  302. return "", err
  303. }
  304. stmt.WriteString(" " + alter_table_options[tokenIndex%len(alter_table_options)])
  305. }
  306. }
  307. return stmt.String(), nil
  308. }
  309. func chooseToken(tokens []string, f *ConsumeFuzzer) (string, error) {
  310. index, err := f.GetInt()
  311. if err != nil {
  312. return "", err
  313. }
  314. var token strings.Builder
  315. token.WriteString(tokens[index%len(tokens)])
  316. if token.String() == "CUSTOM_FUZZ_STRING" {
  317. customFuzzString, err := f.GetString()
  318. if err != nil {
  319. return "", err
  320. }
  321. return customFuzzString, nil
  322. }
  323. // Check if token requires an argument
  324. if containsString(needCustomString, token.String()) {
  325. customFuzzString, err := f.GetString()
  326. if err != nil {
  327. return "", err
  328. }
  329. token.WriteString(" " + customFuzzString)
  330. }
  331. return token.String(), nil
  332. }
  333. var stmtTypes = map[string][][]string{
  334. "DELETE": deleteTokens,
  335. "INSERT": insertTokens,
  336. "SELECT": selectTokens,
  337. "LOAD": loadTokens,
  338. "REPLACE": replaceTokens,
  339. "CREATE": createTokens,
  340. "DROP": dropTokens,
  341. "RENAME": renameTokens,
  342. "TRUNCATE": truncateTokens,
  343. "SET": setTokens,
  344. "ALTER": alterTokens,
  345. "ALTER TABLE": alterTableTokens, // ALTER TABLE has its own set of tokens
  346. }
  347. var stmtTypeEnum = map[int]string{
  348. 0: "DELETE",
  349. 1: "INSERT",
  350. 2: "SELECT",
  351. 3: "LOAD",
  352. 4: "REPLACE",
  353. 5: "CREATE",
  354. 6: "DROP",
  355. 7: "RENAME",
  356. 8: "TRUNCATE",
  357. 9: "SET",
  358. 10: "ALTER",
  359. 11: "ALTER TABLE",
  360. }
  361. func createStmt(f *ConsumeFuzzer) (string, error) {
  362. stmtIndex, err := f.GetInt()
  363. if err != nil {
  364. return "", err
  365. }
  366. stmtIndex = stmtIndex % len(stmtTypes)
  367. queryType := stmtTypeEnum[stmtIndex]
  368. tokens := stmtTypes[queryType]
  369. // We have custom creator for ALTER TABLE
  370. if queryType == "ALTER TABLE" {
  371. query, err := createAlterTableStmt(f)
  372. if err != nil {
  373. return "", err
  374. }
  375. return query, nil
  376. }
  377. // Here we are creating a query that is not
  378. // an 'alter table' query. For available
  379. // queries, see "stmtTypes"
  380. // First specify the first query keyword:
  381. var query strings.Builder
  382. query.WriteString(queryType)
  383. // Next create the args for the
  384. queryArgs, err := createStmtArgs(tokens, f)
  385. if err != nil {
  386. return "", err
  387. }
  388. query.WriteString(" " + queryArgs)
  389. return query.String(), nil
  390. }
  391. // Creates the arguments of a statements. In a select statement
  392. // that would be everything after "select".
  393. func createStmtArgs(tokenslice [][]string, f *ConsumeFuzzer) (string, error) {
  394. var query, token strings.Builder
  395. // We go through the tokens in the tokenslice,
  396. // create the respective token and add it to
  397. // "query"
  398. for _, tokens := range tokenslice {
  399. // For extra randomization, the fuzzer can
  400. // choose to not include this token.
  401. includeThisToken, err := f.GetBool()
  402. if err != nil {
  403. return "", err
  404. }
  405. if !includeThisToken {
  406. continue
  407. }
  408. // There may be several tokens to choose from:
  409. if len(tokens) > 1 {
  410. chosenToken, err := chooseToken(tokens, f)
  411. if err != nil {
  412. return "", err
  413. }
  414. query.WriteString(" " + chosenToken)
  415. } else {
  416. token.WriteString(tokens[0])
  417. // In case the token is "CUSTOM_FUZZ_STRING"
  418. // we will then create a non-structured string
  419. if token.String() == "CUSTOM_FUZZ_STRING" {
  420. customFuzzString, err := f.GetString()
  421. if err != nil {
  422. return "", err
  423. }
  424. query.WriteString(" " + customFuzzString)
  425. continue
  426. }
  427. // Check if token requires an argument.
  428. // Tokens that take an argument can be found
  429. // in 'needCustomString'. If so, we add a
  430. // non-structured string to the token.
  431. if containsString(needCustomString, token.String()) {
  432. customFuzzString, err := f.GetString()
  433. if err != nil {
  434. return "", err
  435. }
  436. token.WriteString(fmt.Sprintf(" %s", customFuzzString))
  437. }
  438. query.WriteString(fmt.Sprintf(" %s", token.String()))
  439. }
  440. }
  441. return query.String(), nil
  442. }
  443. // Creates a semi-structured query. It creates a string
  444. // that is a combination of the keywords and random strings.
  445. func createQuery(f *ConsumeFuzzer) (string, error) {
  446. queryLen, err := f.GetInt()
  447. if err != nil {
  448. return "", err
  449. }
  450. maxLen := queryLen % 60
  451. if maxLen == 0 {
  452. return "", fmt.Errorf("could not create a query")
  453. }
  454. var query strings.Builder
  455. for i := 0; i < maxLen; i++ {
  456. // Get a new token:
  457. useKeyword, err := f.GetBool()
  458. if err != nil {
  459. return "", err
  460. }
  461. if useKeyword {
  462. keyword, err := getKeyword(f)
  463. if err != nil {
  464. return "", err
  465. }
  466. query.WriteString(" " + keyword)
  467. } else {
  468. customString, err := f.GetString()
  469. if err != nil {
  470. return "", err
  471. }
  472. query.WriteString(" " + customString)
  473. }
  474. }
  475. if query.String() == "" {
  476. return "", fmt.Errorf("could not create a query")
  477. }
  478. return query.String(), nil
  479. }
  480. // GetSQLString is the API that users interact with.
  481. //
  482. // Usage:
  483. //
  484. // f := NewConsumer(data)
  485. // sqlString, err := f.GetSQLString()
  486. func (f *ConsumeFuzzer) GetSQLString() (string, error) {
  487. var query string
  488. veryStructured, err := f.GetBool()
  489. if err != nil {
  490. return "", err
  491. }
  492. if veryStructured {
  493. query, err = createStmt(f)
  494. if err != nil {
  495. return "", err
  496. }
  497. } else {
  498. query, err = createQuery(f)
  499. if err != nil {
  500. return "", err
  501. }
  502. }
  503. return query, nil
  504. }