Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit e95ec73

Browse filesBrowse files
committed
add json path
1 parent efec969 commit e95ec73
Copy full SHA for e95ec73

File tree

Expand file treeCollapse file tree

3 files changed

+58
-3
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+58
-3
lines changed

‎sqlalchemy_bigquery/_json.py

Copy file name to clipboardExpand all lines: sqlalchemy_bigquery/_json.py
+15-2Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,26 @@ def _format_value(self, value):
3737
if isinstance(value, int):
3838
value = "$[%s]" % value
3939
else:
40-
value = '$.%s' % value
40+
value = '$."%s"' % value
4141
return value
4242

43-
4443
class JSONIntIndexType(JSONIndexType):
4544
__visit_name__ = "json_int_index"
4645

4746

4847
class JSONStrIndexType(JSONIndexType):
4948
__visit_name__ = "json_str_index"
49+
50+
51+
class JSONPathType(_FormatTypeMixin, sqltypes.JSON.JSONPathType):
52+
def _format_value(self, value):
53+
return "$%s" % (
54+
"".join(
55+
[
56+
"[%s]" % elem if isinstance(elem, int) else '."%s"' % elem
57+
for elem in value
58+
]
59+
)
60+
)
61+
62+

‎sqlalchemy_bigquery/base.py

Copy file name to clipboardExpand all lines: sqlalchemy_bigquery/base.py
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,12 @@ def visit_json_getitem_op_binary(self, binary, operator_, **kw):
588588
self.process(binary.right, **kw),
589589
)
590590

591+
def visit_json_path_getitem_op_binary(self, binary, operator, **kw):
592+
return "JSON_QUERY(%s, %s)" % (
593+
self.process(binary.left, **kw),
594+
self.process(binary.right, **kw),
595+
)
596+
591597
def _get_regexp_args(self, binary, kw):
592598
string = self.process(binary.left, **kw)
593599
pattern = self.process(binary.right, **kw)
@@ -661,6 +667,9 @@ def visit_NUMERIC(self, type_, **kw):
661667
def visit_JSON(self, type_, **kw):
662668
return "JSON"
663669

670+
def visit_json_path(self, type_, **kw):
671+
return "STRING"
672+
664673
def visit_json_int_index(self, type_, **kw):
665674
return "STRING"
666675

@@ -1104,6 +1113,7 @@ class BigQueryDialect(DefaultDialect):
11041113
sqlalchemy.sql.sqltypes.Enum: sqlalchemy.sql.sqltypes.Enum,
11051114
sqlalchemy.sql.sqltypes.JSON: _json.JSON,
11061115
sqlalchemy.sql.sqltypes.JSON.JSONIndexType: _json.JSONIndexType,
1116+
sqlalchemy.sql.sqltypes.JSON.JSONPathType: _json.JSONPathType,
11071117
sqlalchemy.sql.sqltypes.JSON.JSONIntIndexType: _json.JSONIntIndexType,
11081118
sqlalchemy.sql.sqltypes.JSON.JSONStrIndexType: _json.JSONStrIndexType,
11091119
}

‎tests/unit/test__json.py

Copy file name to clipboardExpand all lines: tests/unit/test__json.py
+33-1Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,13 @@ def test_json_insert_type_info(faux_conn, metadata, json_table, json_data):
5656
(
5757
["name"],
5858
"JSON_QUERY(`json_table`.`cart`, %(cart_1:STRING)s)",
59-
"JSON_QUERY(`json_table`.`cart`, '$.name')",
59+
"JSON_QUERY(`json_table`.`cart`, '$.\"name\"')",
6060
),
61+
# (
62+
# ["items", 0],
63+
# "JSON_QUERY(`json_table`.`cart`, %(cart_1:STRING)s)",
64+
# "JSON_QUERY(`json_table`.`cart`, '$.items[0]')",
65+
# ),
6166
),
6267
)
6368
def test_json_index(faux_conn, json_column, index_values, sql, literal_sql):
@@ -75,6 +80,33 @@ def test_json_index(faux_conn, json_column, index_values, sql, literal_sql):
7580
assert expected_sql == actual_sql
7681
assert expected_literal_sql == actual_literal_sql
7782

83+
@pytest.mark.parametrize(
84+
"index_values,sql,literal_sql",
85+
(
86+
(
87+
["name"],
88+
"JSON_QUERY(`json_table`.`cart`, %(cart_1:STRING)s)",
89+
"JSON_QUERY(`json_table`.`cart`, '$.\"name\"')",
90+
),
91+
# (
92+
# ["items", 0],
93+
# "JSON_QUERY(`json_table`.`cart`, %(cart_1:STRING)s)",
94+
# "JSON_QUERY(`json_table`.`cart`, '$.items[0]')",
95+
# ),
96+
),
97+
)
98+
def test_json_path(faux_conn, json_column, index_values, sql, literal_sql):
99+
expr = json_column[index_values]
100+
101+
expected_sql = f"SELECT {sql} AS `anon_1` \nFROM `json_table`"
102+
expected_literal_sql = f"SELECT {literal_sql} AS `anon_1` \nFROM `json_table`"
103+
104+
actual_sql = sqlalchemy.select(expr).compile(faux_conn).string
105+
actual_literal_sql = sqlalchemy.select(expr).compile(faux_conn, compile_kwargs={"literal_binds": True}).string
106+
107+
assert expected_sql == actual_sql
108+
assert expected_literal_sql == actual_literal_sql
109+
78110
# TODO: AFAICT, JSON is not a supported query parameter type - enforce this
79111

80112
# TODO: Test _json_serializer set from create_engine

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.