22Experimental
33Work in progress, breaking changes are possible.
44"""
5+
56import collections
67import collections .abc
78from typing import Any , Dict , List , Mapping , Optional , Sequence , Tuple , Type , Union
@@ -63,6 +64,9 @@ def __init__(self, dialect):
6364
6465
6566class YqlTypeCompiler (StrSQLTypeCompiler ):
67+ def visit_JSON (self , type_ : Union [sa .JSON , types .YqlJSON ], ** kw ):
68+ return "JSON"
69+
6670 def visit_CHAR (self , type_ : sa .CHAR , ** kw ):
6771 return "UTF8"
6872
@@ -171,8 +175,21 @@ def get_ydb_type(
171175 ydb_type = ydb .PrimitiveType .Int64
172176 # Integers
173177
178+ # Json
174179 elif isinstance (type_ , sa .JSON ):
175180 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+
176193 elif isinstance (type_ , sa .DateTime ):
177194 ydb_type = ydb .PrimitiveType .Timestamp
178195 elif isinstance (type_ , sa .Date ):
@@ -326,6 +343,24 @@ def visit_function(self, func, add_to_result_map=None, **kwargs):
326343 + [name ]
327344 ) % {"expr" : self .function_argspec (func , ** kwargs )}
328345
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+
329364 def visit_regexp_match_op_binary (self , binary , operator , ** kw ):
330365 return self ._generate_generic_binary (binary , " REGEXP " , ** kw )
331366
@@ -336,7 +371,7 @@ def _is_bound_to_nullable_column(self, bind_name: str) -> bool:
336371 if bind_name in self .column_keys and hasattr (self .compile_state , "dml_table" ):
337372 if bind_name in self .compile_state .dml_table .c :
338373 column = self .compile_state .dml_table .c [bind_name ]
339- return not column .primary_key
374+ return column . nullable and not column .primary_key
340375 return False
341376
342377 def _guess_bound_variable_type_by_parameters (
@@ -503,6 +538,7 @@ class YqlDialect(StrCompileDialect):
503538 supports_smallserial = False
504539 supports_schemas = False
505540 supports_constraint_comments = False
541+ supports_json_type = True
506542
507543 insert_returning = False
508544 update_returning = False
@@ -524,6 +560,10 @@ class YqlDialect(StrCompileDialect):
524560 statement_compiler = YqlCompiler
525561 ddl_compiler = YqlDDLCompiler
526562 type_compiler = YqlTypeCompiler
563+ colspecs = {
564+ sa .types .JSON : types .YqlJSON ,
565+ sa .types .JSON .JSONPathType : types .YqlJSON .YqlJSONPathType ,
566+ }
527567
528568 construct_arguments = [
529569 (
@@ -544,6 +584,12 @@ class YqlDialect(StrCompileDialect):
544584 def import_dbapi (cls : Any ):
545585 return dbapi .YdbDBApi ()
546586
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+
547593 def _describe_table (self , connection , table_name , schema = None ):
548594 if schema is not None :
549595 raise dbapi .NotSupportedError ("unsupported on non empty schema" )
0 commit comments