Skip to content

Commit 705fca0

Browse files
committed
feat: support load data in mysql
1 parent 115a950 commit 705fca0

7 files changed

Lines changed: 141 additions & 2 deletions

File tree

pegjs/mysql.pegjs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,7 @@ cmd_stmt
447447
/ grant_stmt
448448
/ explain_stmt
449449
/ transaction_stmt
450+
/ load_data_stmt
450451

451452
create_stmt
452453
= create_table_stmt
@@ -2091,6 +2092,69 @@ transaction_stmt
20912092
}
20922093
}
20932094

2095+
load_data_field
2096+
= k:('FIELDS'i / 'COLUMNS'i) __ t:('TERMINATED'i __ 'BY'i __ ident_without_kw_type)? __ en:(('OPTIONALLY'i)? __ 'ENCLOSED'i __ 'BY'i __ ident_without_kw_type)? __ es:('ESCAPED'i __ 'BY'i __ ident_without_kw_type)? {
2097+
if (t) t[4].prefix = 'TERMINATED BY'
2098+
if (en) en[6].prefix = `${en[0] && en[0].toUpperCase() === 'OPTIONALLY' ? 'OPTIONALLY ' : ''}ENCLOSED BY`
2099+
if (es) es[4].prefix = 'ESCAPED BY'
2100+
return {
2101+
keyword: k,
2102+
terminated: t && t[4],
2103+
enclosed: en && en[6],
2104+
escaped: es && es[4]
2105+
}
2106+
}
2107+
2108+
load_data_line_starting
2109+
= k:('STARTING'i / 'TERMINATED'i) __ 'BY'i __ s:ident_without_kw_type {
2110+
s.prefix = `${k.toUpperCase()} BY`
2111+
return {
2112+
type: k.toLowerCase(),
2113+
[k.toLowerCase()]: s
2114+
}
2115+
}
2116+
load_data_line
2117+
= k:'LINES'i __ s:load_data_line_starting? __ t:load_data_line_starting? {
2118+
if (s && t && s.type === t.type) throw new Error('LINES cannot be specified twice')
2119+
if (s) Reflect.deleteProperty(s, 'type')
2120+
if (t) Reflect.deleteProperty(t, 'type')
2121+
return {
2122+
keyword: k,
2123+
...(s || {}),
2124+
...(t || {})
2125+
}
2126+
}
2127+
2128+
load_data_stmt
2129+
= 'LOAD'i __ 'DATA'i __ lc:('LOW_PRIORITY'i / 'CONCURRENT'i)? __ lo:('LOCAL'i)? __
2130+
'INFILE'i __ file:ident_without_kw_type __ ri:replace_insert? __
2131+
'INTO'i __ 'TABLE'i __ table:table_name __
2132+
pa:insert_partition? __
2133+
cs:(create_option_character_set_kw __ ident_without_kw_type)? __
2134+
fields:load_data_field? __
2135+
lines:load_data_line? __
2136+
ig:(KW_IGNORE __ literal_numeric __ ('LINES'i / 'ROWS'i))? __
2137+
co:column_clause? __
2138+
set:(KW_SET __ set_list)? {
2139+
return {
2140+
type: 'load_data',
2141+
mode: lc,
2142+
local: lo,
2143+
file: file,
2144+
replace_ignore: ri,
2145+
table: table,
2146+
partition: pa,
2147+
character_set: cs,
2148+
fields: fields,
2149+
lines: lines,
2150+
ignore: ig && {
2151+
count: ig[2],
2152+
suffix: ig[4]
2153+
},
2154+
column: co,
2155+
set: set && set[2]
2156+
}
2157+
}
20942158
priv_type_table
20952159
= p:(KW_ALL / KW_ALTER / KW_CREATE __ 'VIEW'i / KW_CREATE / KW_DELETE / KW_DROP / 'GRANT'i __ 'OPTION'i / KW_INDEX / KW_INSERT / KW_REFERENCES / KW_SELECT / KW_SHOW __ KW_VIEW / KW_TRIGGER / KW_UPDATE) {
20962160
return {
@@ -3372,7 +3436,7 @@ double_quoted_ident
33723436
}
33733437

33743438
single_quoted_ident
3375-
= "'" chars:[^']+ "'" {
3439+
= "'" chars:[^']* "'" {
33763440
return {
33773441
type: 'single_quote_string',
33783442
value: chars.join('')

src/expr.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { jsonExprToSQL, jsonVisitorExprToSQL } from './json'
1212
import { selectToSQL } from './select'
1313
import { showToSQL } from './show'
1414
import { arrayStructExprToSQL } from './array-struct'
15+
import { loadDataToSQL } from './load'
1516
import { tablesToSQL, unnestToSQL } from './tables'
1617
import { unionToSQL } from './union'
1718
import { namedWindowExprListToSQL, windowFuncToSQL } from './window'
@@ -35,6 +36,7 @@ const exprToSQLConvertFn = {
3536
fulltext_search : fullTextSearchToSQL,
3637
function : funcToSQL,
3738
lambda : lambdaToSQL,
39+
load_data : loadDataToSQL,
3840
insert : unionToSQL,
3941
interval : intervalToSQL,
4042
json : jsonExprToSQL,

src/insert.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,6 @@ function insertToSQL(stmt) {
8989
export {
9090
conflictToSQL,
9191
insertToSQL,
92+
partitionToSQL,
9293
valuesToSQL,
9394
}

src/load.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { commonOptionConnector, toUpper, hasVal, literalToSQL } from './util'
2+
import { partitionToSQL } from './insert'
3+
import { tableToSQL } from './tables'
4+
import { columnsToSQL } from './column'
5+
import { setToSQL } from './update'
6+
7+
function loadDataFields(expr) {
8+
if (!expr) return ''
9+
const { keyword, terminated, enclosed, escaped } = expr
10+
return [
11+
toUpper(keyword),
12+
literalToSQL(terminated),
13+
literalToSQL(enclosed),
14+
literalToSQL(escaped),
15+
].filter(hasVal).join(' ')
16+
}
17+
18+
function loadDataLines(expr) {
19+
if (!expr) return ''
20+
const { keyword, starting, terminated } = expr
21+
return [
22+
toUpper(keyword),
23+
literalToSQL(starting),
24+
literalToSQL(terminated),
25+
].filter(hasVal).join(' ')
26+
}
27+
28+
function loadDataIgnore(expr) {
29+
if (!expr) return ''
30+
const { count, suffix } = expr
31+
return ['IGNORE', literalToSQL(count), suffix].filter(hasVal).join(' ')
32+
}
33+
function loadDataToSQL(expr) {
34+
if (!expr) return ''
35+
const { mode, local, file, replace_ignore, table, partition, character_set, column, fields, lines, set, ignore } = expr
36+
const result = [
37+
'LOAD DATA',
38+
toUpper(mode),
39+
toUpper(local),
40+
'INFILE',
41+
literalToSQL(file),
42+
toUpper(replace_ignore),
43+
'INTO TABLE',
44+
tableToSQL(table),
45+
partitionToSQL(partition),
46+
commonOptionConnector('CHARACTER SET', literalToSQL, character_set),
47+
loadDataFields(fields),
48+
loadDataLines(lines),
49+
loadDataIgnore(ignore),
50+
columnsToSQL(column),
51+
commonOptionConnector('SET', setToSQL, set),
52+
]
53+
return result.filter(hasVal).join(' ')
54+
}
55+
56+
export {
57+
loadDataToSQL,
58+
}

src/sql.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { unionToSQL, multipleToSQL } from './union'
22

3-
const supportedTypes = ['analyze', 'attach', 'select', 'deallocate', 'delete', 'exec', 'update', 'insert', 'drop', 'rename', 'truncate', 'call', 'desc', 'use', 'alter', 'set', 'create', 'lock', 'unlock', 'declare', 'show', 'replace', 'if', 'grant', 'revoke', 'proc', 'raise', 'execute', 'transaction', 'explain', 'comment']
3+
const supportedTypes = ['analyze', 'attach', 'select', 'deallocate', 'delete', 'exec', 'update', 'insert', 'drop', 'rename', 'truncate', 'call', 'desc', 'use', 'alter', 'set', 'create', 'lock', 'unlock', 'declare', 'show', 'replace', 'if', 'grant', 'revoke', 'proc', 'raise', 'execute', 'transaction', 'explain', 'comment', 'load_data']
44

55
function checkSupported(expr) {
66
const ast = expr && expr.ast ? expr.ast : expr

src/union.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
import { execToSQL } from './exec'
2727
import { orderOrPartitionByToSQL } from './expr'
2828
import { limitToSQL } from './limit'
29+
import { loadDataToSQL } from './load'
2930
import { procToSQL } from './proc'
3031
import { transactionToSQL } from './transaction'
3132
import { showToSQL } from './show'
@@ -47,6 +48,7 @@ const typeToSQLFn = {
4748
update : updateToSQL,
4849
if : ifToSQL,
4950
insert : insertToSQL,
51+
load_data : loadDataToSQL,
5052
drop : commonCmdToSQL,
5153
truncate : commonCmdToSQL,
5254
replace : insertToSQL,

test/mysql-mariadb.spec.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,18 @@ describe('mysql', () => {
14031403
expect(selectIntoToSQL({})).to.be.undefined
14041404
})
14051405
})
1406+
describe('load data', () => {
1407+
it('should support load data', () => {
1408+
let sql = `LOAD DATA LOCAL INFILE "mycsv.csv" INTO TABLE mytable FIELDS TERMINATED BY ' ^' IGNORE 1 LINES;`
1409+
expect(getParsedSql(sql)).to.equal(`LOAD DATA LOCAL INFILE "mycsv.csv" INTO TABLE \`mytable\` FIELDS TERMINATED BY ' ^' IGNORE 1 LINES`)
1410+
sql = `LOAD DATA LOW_PRIORITY LOCAL INFILE '/tmp/test.txt' REPLACE INTO TABLE test FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES TERMINATED BY '\n' STARTING BY ''`
1411+
expect(getParsedSql(sql)).to.equal("LOAD DATA LOW_PRIORITY LOCAL INFILE '/tmp/test.txt' REPLACE INTO TABLE `test` FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\' LINES STARTING BY '' TERMINATED BY '\n'")
1412+
sql = `LOAD DATA LOW_PRIORITY LOCAL INFILE '/tmp/test.txt' REPLACE INTO TABLE test FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '' ESCAPED BY '\\' IGNORE 1 LINES (column1, @var1) SET column2 = @var1/100;`
1413+
expect(getParsedSql(sql)).to.equal("LOAD DATA LOW_PRIORITY LOCAL INFILE '/tmp/test.txt' REPLACE INTO TABLE \`test\` FIELDS TERMINATED BY '\t' OPTIONALLY ENCLOSED BY '' ESCAPED BY '\\' IGNORE 1 LINES (`column1`, @var1) SET `column2` = @var1 / 100")
1414+
sql = `LOAD DATA INFILE 'file.txt' INTO TABLE t1 (column1, column2) SET column3 = CURRENT_TIMESTAMP;`
1415+
expect(getParsedSql(sql)).to.equal("LOAD DATA INFILE 'file.txt' INTO TABLE `t1` (`column1`, `column2`) SET `column3` = CURRENT_TIMESTAMP")
1416+
})
1417+
})
14061418
})
14071419

14081420
})

0 commit comments

Comments
 (0)