2
2
Experimental
3
3
Work in progress, breaking changes are possible.
4
4
"""
5
+
5
6
import collections
6
7
import collections .abc
7
8
from typing import Any , Dict , List , Mapping , Optional , Sequence , Tuple , Type , Union
@@ -63,6 +64,9 @@ def __init__(self, dialect):
63
64
64
65
65
66
class YqlTypeCompiler (StrSQLTypeCompiler ):
67
+ def visit_JSON (self , type_ : Union [sa .JSON , types .YqlJSON ], ** kw ):
68
+ return "JSON"
69
+
66
70
def visit_CHAR (self , type_ : sa .CHAR , ** kw ):
67
71
return "UTF8"
68
72
@@ -171,8 +175,21 @@ def get_ydb_type(
171
175
ydb_type = ydb .PrimitiveType .Int64
172
176
# Integers
173
177
178
+ # Json
174
179
elif isinstance (type_ , sa .JSON ):
175
180
ydb_type = ydb .PrimitiveType .Json
181
+ elif isinstance (type_ , sa .JSON .JSONStrIndexType ):
182
+ ydb_type = ydb .PrimitiveType .Utf8
183
+ elif isinstance (type_ , sa .JSON .JSONIntIndexType ):
184
+ ydb_type = ydb .PrimitiveType .Int64
185
+ elif isinstance (type_ , sa .JSON .JSONPathType ):
186
+ ydb_type = ydb .PrimitiveType .Utf8
187
+ elif isinstance (type_ , types .YqlJSON ):
188
+ ydb_type = ydb .PrimitiveType .Json
189
+ elif isinstance (type_ , types .YqlJSON .YqlJSONPathType ):
190
+ ydb_type = ydb .PrimitiveType .Utf8
191
+ # Json
192
+
176
193
elif isinstance (type_ , sa .DateTime ):
177
194
ydb_type = ydb .PrimitiveType .Timestamp
178
195
elif isinstance (type_ , sa .Date ):
@@ -326,6 +343,24 @@ def visit_function(self, func, add_to_result_map=None, **kwargs):
326
343
+ [name ]
327
344
) % {"expr" : self .function_argspec (func , ** kwargs )}
328
345
346
+ def _yson_convert_to (self , statement : str , target_type : sa .types .TypeEngine ) -> str :
347
+ type_name = target_type .compile (self .dialect )
348
+ if isinstance (target_type , sa .Numeric ) and not isinstance (target_type , (sa .Float , sa .Double )):
349
+ # Since Decimal is stored in JSON either as String or as Float
350
+ string_value = f"Yson::ConvertTo({ statement } , Optional<String>, Yson::Options(true AS AutoConvert))"
351
+ return f"CAST({ string_value } AS Optional<{ type_name } >)"
352
+ return f"Yson::ConvertTo({ statement } , Optional<{ type_name } >)"
353
+
354
+ def visit_json_getitem_op_binary (self , binary : sa .BinaryExpression , operator , ** kw ) -> str :
355
+ json_field = self .process (binary .left , ** kw )
356
+ index = self .process (binary .right , ** kw )
357
+ return self ._yson_convert_to (f"{ json_field } [{ index } ]" , binary .type )
358
+
359
+ def visit_json_path_getitem_op_binary (self , binary : sa .BinaryExpression , operator , ** kw ) -> str :
360
+ json_field = self .process (binary .left , ** kw )
361
+ path = self .process (binary .right , ** kw )
362
+ return self ._yson_convert_to (f"Yson::YPath({ json_field } , { path } )" , binary .type )
363
+
329
364
def visit_regexp_match_op_binary (self , binary , operator , ** kw ):
330
365
return self ._generate_generic_binary (binary , " REGEXP " , ** kw )
331
366
@@ -336,7 +371,7 @@ def _is_bound_to_nullable_column(self, bind_name: str) -> bool:
336
371
if bind_name in self .column_keys and hasattr (self .compile_state , "dml_table" ):
337
372
if bind_name in self .compile_state .dml_table .c :
338
373
column = self .compile_state .dml_table .c [bind_name ]
339
- return not column .primary_key
374
+ return column . nullable and not column .primary_key
340
375
return False
341
376
342
377
def _guess_bound_variable_type_by_parameters (
@@ -503,6 +538,7 @@ class YqlDialect(StrCompileDialect):
503
538
supports_smallserial = False
504
539
supports_schemas = False
505
540
supports_constraint_comments = False
541
+ supports_json_type = True
506
542
507
543
insert_returning = False
508
544
update_returning = False
@@ -524,6 +560,10 @@ class YqlDialect(StrCompileDialect):
524
560
statement_compiler = YqlCompiler
525
561
ddl_compiler = YqlDDLCompiler
526
562
type_compiler = YqlTypeCompiler
563
+ colspecs = {
564
+ sa .types .JSON : types .YqlJSON ,
565
+ sa .types .JSON .JSONPathType : types .YqlJSON .YqlJSONPathType ,
566
+ }
527
567
528
568
construct_arguments = [
529
569
(
@@ -544,6 +584,12 @@ class YqlDialect(StrCompileDialect):
544
584
def import_dbapi (cls : Any ):
545
585
return dbapi .YdbDBApi ()
546
586
587
+ def __init__ (self , json_serializer = None , json_deserializer = None , ** kwargs ):
588
+ super ().__init__ (** kwargs )
589
+
590
+ self ._json_deserializer = json_deserializer
591
+ self ._json_serializer = json_serializer
592
+
547
593
def _describe_table (self , connection , table_name , schema = None ):
548
594
if schema is not None :
549
595
raise dbapi .NotSupportedError ("unsupported on non empty schema" )
0 commit comments