|
4 | 4 | # license that can be found in the LICENSE file or at
|
5 | 5 | # https://developers.google.com/open-source/licenses/bsd
|
6 | 6 |
|
| 7 | +"SQL parsing and classification utils." |
| 8 | + |
7 | 9 | import datetime
|
8 | 10 | import decimal
|
9 | 11 | import re
|
|
23 | 25 | STMT_INSERT = "INSERT"
|
24 | 26 |
|
25 | 27 | # Heuristic for identifying statements that don't need to be run as updates.
|
26 |
| -re_NON_UPDATE = re.compile(r"^\s*(SELECT)", re.IGNORECASE) |
| 28 | +RE_NON_UPDATE = re.compile(r"^\s*(SELECT)", re.IGNORECASE) |
27 | 29 |
|
28 |
| -re_WITH = re.compile(r"^\s*(WITH)", re.IGNORECASE) |
| 30 | +RE_WITH = re.compile(r"^\s*(WITH)", re.IGNORECASE) |
29 | 31 |
|
30 | 32 | # DDL statements follow
|
31 | 33 | # https://cloud.google.com/spanner/docs/data-definition-language
|
32 |
| -re_DDL = re.compile(r"^\s*(CREATE|ALTER|DROP)", re.IGNORECASE | re.DOTALL) |
33 |
| - |
34 |
| -re_IS_INSERT = re.compile(r"^\s*(INSERT)", re.IGNORECASE | re.DOTALL) |
| 34 | +RE_DDL = re.compile(r"^\s*(CREATE|ALTER|DROP)", re.IGNORECASE | re.DOTALL) |
35 | 35 |
|
| 36 | +RE_IS_INSERT = re.compile(r"^\s*(INSERT)", re.IGNORECASE | re.DOTALL) |
36 | 37 |
|
37 |
| -def classify_stmt(sql): |
38 |
| - if re_DDL.match(sql): |
39 |
| - return STMT_DDL |
40 |
| - elif re_IS_INSERT.match(sql): |
41 |
| - return STMT_INSERT |
42 |
| - elif re_NON_UPDATE.match(sql): |
43 |
| - return STMT_NON_UPDATING |
44 |
| - elif re_WITH.match(sql): |
45 |
| - # As of Fri-13th-March-2020, Cloud Spanner only supports WITH for DQL |
46 |
| - # statements and doesn't yet support WITH for DML statements. |
47 |
| - # When WITH for DML is added, we'll need to update this classifier |
48 |
| - # accordingly. |
49 |
| - return STMT_NON_UPDATING |
50 |
| - else: |
51 |
| - return STMT_UPDATING |
52 |
| - |
53 |
| - |
54 |
| -re_INSERT = re.compile( |
| 38 | +RE_INSERT = re.compile( |
55 | 39 | # Only match the `INSERT INTO <table_name> (columns...)
|
56 | 40 | # otherwise the rest of the statement could be a complex
|
57 | 41 | # operation.
|
58 | 42 | r"^\s*INSERT INTO (?P<table_name>[^\s\(\)]+)\s*\((?P<columns>[^\(\)]+)\)",
|
59 | 43 | re.IGNORECASE | re.DOTALL,
|
60 | 44 | )
|
61 | 45 |
|
62 |
| -re_VALUES_TILL_END = re.compile(r"VALUES\s*\(.+$", re.IGNORECASE | re.DOTALL) |
| 46 | +RE_VALUES_TILL_END = re.compile(r"VALUES\s*\(.+$", re.IGNORECASE | re.DOTALL) |
63 | 47 |
|
64 |
| -re_VALUES_PYFORMAT = re.compile( |
| 48 | +RE_VALUES_PYFORMAT = re.compile( |
65 | 49 | # To match: (%s, %s,....%s)
|
66 | 50 | r"(\(\s*%s[^\(\)]+\))",
|
67 | 51 | re.DOTALL,
|
68 | 52 | )
|
69 | 53 |
|
70 | 54 |
|
| 55 | +def classify_stmt(query): |
| 56 | + """Determine SQL query type. |
| 57 | +
|
| 58 | + :type query: :class:`str` |
| 59 | + :param query: SQL query. |
| 60 | +
|
| 61 | + :rtype: :class:`str` |
| 62 | + :returns: Query type name. |
| 63 | + """ |
| 64 | + if RE_DDL.match(query): |
| 65 | + return STMT_DDL |
| 66 | + |
| 67 | + if RE_IS_INSERT.match(query): |
| 68 | + return STMT_INSERT |
| 69 | + |
| 70 | + if RE_NON_UPDATE.match(query) or RE_WITH.match(query): |
| 71 | + # As of 13-March-2020, Cloud Spanner only supports WITH for DQL |
| 72 | + # statements and doesn't yet support WITH for DML statements. |
| 73 | + return STMT_NON_UPDATING |
| 74 | + |
| 75 | + return STMT_UPDATING |
| 76 | + |
| 77 | + |
| 78 | +def strip_backticks(name): |
| 79 | + """ |
| 80 | + Strip backticks off of quoted names For example, '`no`' (a Spanner reserved |
| 81 | + word) becomes 'no'. |
| 82 | + """ |
| 83 | + has_quotes = name.startswith("`") and name.endswith("`") |
| 84 | + return name[1:-1] if has_quotes else name |
| 85 | + |
| 86 | + |
71 | 87 | def parse_insert(insert_sql, params):
|
72 | 88 | """
|
73 | 89 | Parse an INSERT statement an generate a list of tuples of the form:
|
@@ -126,14 +142,14 @@ def parse_insert(insert_sql, params):
|
126 | 142 | ],
|
127 | 143 | }
|
128 | 144 | """ # noqa
|
129 |
| - match = re_INSERT.search(insert_sql) |
| 145 | + match = RE_INSERT.search(insert_sql) |
130 | 146 |
|
131 | 147 | if not match:
|
132 | 148 | raise ProgrammingError(
|
133 | 149 | "Could not parse an INSERT statement from %s" % insert_sql
|
134 | 150 | )
|
135 | 151 |
|
136 |
| - after_values_sql = re_VALUES_TILL_END.findall(insert_sql) |
| 152 | + after_values_sql = RE_VALUES_TILL_END.findall(insert_sql) |
137 | 153 | if not after_values_sql:
|
138 | 154 | # Case b)
|
139 | 155 | insert_sql = sanitize_literals_for_upload(insert_sql)
|
|
0 commit comments