Skip to content

Commit e7d0b43

Browse files
committed
Fix: only backtick-quote reserved keywords or names with special characters
1 parent b580f6f commit e7d0b43

5 files changed

Lines changed: 80 additions & 75 deletions

File tree

README.md

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ By generating an explicit `SELECT` statement that recursively expands `RECORD` a
1616

1717
- `jq` installed on your system.
1818
- `bash` shell.
19+
- `bq` (Google Cloud SDK) for direct table schema fetching.
1920

2021
### Usage
2122

@@ -125,43 +126,43 @@ Input `my_schema.json`:
125126
Generates:
126127
```sql
127128
SELECT
128-
`A`,
129-
`B`,
129+
A,
130+
B,
130131
STRUCT(
131132
STRUCT(
132-
`C`.`D`.`E`,
133+
C.D.E,
133134
ARRAY(
134135
SELECT AS STRUCT
135-
`F`.`G`
136+
F.G
136137
FROM
137-
UNNEST(`C`.`D`.`F`) AS `F`
138+
UNNEST(C.D.F) AS F
138139
WITH
139140
OFFSET
140141
ORDER BY
141142
OFFSET
142-
) AS `F`
143-
) AS `D`,
144-
`C`.`H`
145-
) AS `C`,
143+
) AS F
144+
) AS D,
145+
C.H
146+
) AS C,
146147
STRUCT(
147-
`I`.`J`,
148-
`I`.`K`
149-
) AS `I`,
148+
I.J,
149+
I.K
150+
) AS I,
150151
ARRAY(
151152
SELECT AS STRUCT
152-
`L`.`M`,
153-
`L`.`N`,
153+
L.M,
154+
L.N,
154155
STRUCT(
155-
`L`.`O`.`P`
156-
) AS `O`
156+
L.O.P
157+
) AS O
157158
FROM
158-
UNNEST(`L`) AS `L`
159+
UNNEST(L) AS L
159160
WITH
160161
OFFSET
161162
ORDER BY
162163
OFFSET
163-
) AS `L`,
164-
`Q`
164+
) AS L,
165+
Q
165166
```
166167

167168
In case you would like to use snake_case for field names use flag `--use_snake_case`:

bin/bigquery-schema-select

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ if [[ "$1" == "--use_snake_case" ]]; then
3737
fi
3838

3939
echo "$INPUT" | jq -r --argjson useSnakeCase "$USE_SNAKE_CASE" '
40-
def quoteName(name): "`" + name + "`";
40+
def isReserved(name):
41+
name | ascii_downcase | test("^(all|and|any|array|as|asc|assert_rows_modified|at|between|by|case|cast|check|collate|column|contains|create|cross|cube|current|date|default|define|desc|distinct|else|end|enum|escape|except|exclude|exists|extract|false|fetch|following|for|from|full|group|grouping|groups|having|if|ignore|in|inner|intersect|interval|into|is|join|lateral|left|like|limit|lookup|merge|natural|new|no|not|null|nulls|of|on|or|order|outer|over|partition|preceding|proto|range|recursive|respect|right|rollup|rows|select|set|some|struct|tablesample|then|to|treat|true|unbounded|union|unnest|using|when|where|window|with|within)$");
42+
43+
def quoteIfNeeded(name):
44+
if (isReserved(name) or (name | test("[^a-zA-Z0-9_]"))) then "`" + name + "`" else name end;
4145
4246
def calculateFieldName(name; useSnakeCase):
4347
if (useSnakeCase | not) then name
@@ -48,16 +52,16 @@ echo "$INPUT" | jq -r --argjson useSnakeCase "$USE_SNAKE_CASE" '
4852
end;
4953
5054
def toSelectClauseRecursive(current; depth; prefix; useSnakeCase):
51-
(if (prefix == null) then quoteName(current.name) else prefix + "." + quoteName(current.name) end) as $fullyQualifiedName |
55+
(if (prefix == null) then quoteIfNeeded(current.name) else prefix + "." + quoteIfNeeded(current.name) end) as $fullyQualifiedName |
5256
(" " * depth) as $indent |
5357
calculateFieldName(current.name; useSnakeCase) as $calculatedFieldName |
54-
quoteName($calculatedFieldName) as $quotedCalculatedFieldName |
58+
quoteIfNeeded($calculatedFieldName) as $quotedCalculatedFieldName |
5559
(if (useSnakeCase and $calculatedFieldName != current.name) then " AS " + $quotedCalculatedFieldName else "" end) as $alias |
5660
5761
if (current.type != "RECORD") then
5862
$indent + $fullyQualifiedName + $alias
5963
elif (current.type == "RECORD" and current.mode == "REPEATED") then
60-
quoteName(current.name) as $currentNameQuoted |
64+
quoteIfNeeded(current.name) as $currentNameQuoted |
6165
($indent + "ARRAY(\n" +
6266
$indent + " SELECT AS STRUCT\n" +
6367
(current.fields | map(toSelectClauseRecursive(.; depth + 2; $currentNameQuoted; useSnakeCase)) | join(",\n")) + "\n" +

test-resources/my_camel_select.sql

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
SELECT
2-
`AField1` AS `a_field1`,
3-
`BField2` AS `b_field2`,
2+
AField1 AS a_field1,
3+
BField2 AS b_field2,
44
STRUCT(
55
STRUCT(
6-
`CField3`.`DField4`.`EField5` AS `e_field5`,
6+
CField3.DField4.EField5 AS e_field5,
77
ARRAY(
88
SELECT AS STRUCT
9-
`FField6`.`GField7` AS `g_field7`
9+
FField6.GField7 AS g_field7
1010
FROM
11-
UNNEST(`CField3`.`DField4`.`FField6`) AS `FField6`
11+
UNNEST(CField3.DField4.FField6) AS FField6
1212
WITH
1313
OFFSET
1414
ORDER BY
1515
OFFSET
16-
) AS `f_field6`
17-
) AS `d_field4`,
18-
`CField3`.`HField8` AS `h_field8`
19-
) AS `c_field3`,
16+
) AS f_field6
17+
) AS d_field4,
18+
CField3.HField8 AS h_field8
19+
) AS c_field3,
2020
STRUCT(
21-
`IField9`.`JField10` AS `j_field10`,
22-
`IField9`.`KField11` AS `k_field11`
23-
) AS `i_field9`,
21+
IField9.JField10 AS j_field10,
22+
IField9.KField11 AS k_field11
23+
) AS i_field9,
2424
ARRAY(
2525
SELECT AS STRUCT
26-
`LField12`.`MField13` AS `m_field13`,
27-
`LField12`.`NField14` AS `n_field14`,
26+
LField12.MField13 AS m_field13,
27+
LField12.NField14 AS n_field14,
2828
STRUCT(
29-
`LField12`.`OField15`.`PField16` AS `p_field16`
30-
) AS `o_field15`
29+
LField12.OField15.PField16 AS p_field16
30+
) AS o_field15
3131
FROM
32-
UNNEST(`LField12`) AS `LField12`
32+
UNNEST(LField12) AS LField12
3333
WITH
3434
OFFSET
3535
ORDER BY
3636
OFFSET
37-
) AS `l_field12`,
38-
`QField17` AS `q_field17`
37+
) AS l_field12,
38+
QField17 AS q_field17
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
SELECT
2-
`myPrimitive` AS `my_primitive`,
2+
myPrimitive AS my_primitive,
33
STRUCT(
4-
`myStruct`.`myA` AS `my_a`,
5-
`myStruct`.`myB` AS `my_b`
6-
) AS `my_struct`,
4+
myStruct.myA AS my_a,
5+
myStruct.myB AS my_b
6+
) AS my_struct,
77
STRUCT(
8-
`myStructOfStruct`.`my1`,
8+
myStructOfStruct.my1,
99
STRUCT(
10-
`myStructOfStruct`.`my2Struct`.`my2`
11-
) AS `my2_struct`
12-
) AS `my_struct_of_struct`,
13-
`myRepeatedPrimitive` AS `my_repeated_primitive`,
10+
myStructOfStruct.my2Struct.my2
11+
) AS my2_struct
12+
) AS my_struct_of_struct,
13+
myRepeatedPrimitive AS my_repeated_primitive,
1414
ARRAY(
1515
SELECT AS STRUCT
16-
`myRepeatedStruct`.`myX` AS `my_x`,
17-
`myRepeatedStruct`.`myY` AS `my_y`
16+
myRepeatedStruct.myX AS my_x,
17+
myRepeatedStruct.myY AS my_y
1818
FROM
19-
UNNEST(`myRepeatedStruct`) AS `myRepeatedStruct`
19+
UNNEST(myRepeatedStruct) AS myRepeatedStruct
2020
WITH
2121
OFFSET
2222
ORDER BY
2323
OFFSET
24-
) AS `my_repeated_struct`,
25-
`nochange`
24+
) AS my_repeated_struct,
25+
nochange

test-resources/my_select.sql

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
SELECT
2-
`A`,
3-
`B`,
2+
A,
3+
B,
44
STRUCT(
55
STRUCT(
6-
`C`.`D`.`E`,
6+
C.D.E,
77
ARRAY(
88
SELECT AS STRUCT
9-
`F`.`G`
9+
F.G
1010
FROM
11-
UNNEST(`C`.`D`.`F`) AS `F`
11+
UNNEST(C.D.F) AS F
1212
WITH
1313
OFFSET
1414
ORDER BY
1515
OFFSET
16-
) AS `F`
17-
) AS `D`,
18-
`C`.`H`
19-
) AS `C`,
16+
) AS F
17+
) AS D,
18+
C.H
19+
) AS C,
2020
STRUCT(
21-
`I`.`J`,
22-
`I`.`K`
23-
) AS `I`,
21+
I.J,
22+
I.K
23+
) AS I,
2424
ARRAY(
2525
SELECT AS STRUCT
26-
`L`.`M`,
27-
`L`.`N`,
26+
L.M,
27+
L.N,
2828
STRUCT(
29-
`L`.`O`.`P`
30-
) AS `O`
29+
L.O.P
30+
) AS O
3131
FROM
32-
UNNEST(`L`) AS `L`
32+
UNNEST(L) AS L
3333
WITH
3434
OFFSET
3535
ORDER BY
3636
OFFSET
37-
) AS `L`,
38-
`Q`
37+
) AS L,
38+
Q

0 commit comments

Comments
 (0)