def new_array_set(elements: typing.List[irast.Base], *, ctx: context.ContextLevel) -> irast.Set: arr = irast.Array(elements=elements) if elements: stype = inference.infer_type(arr, env=ctx.env) typeref = irtyputils.type_to_typeref(ctx.env.schema, stype) else: stype = typeref = None arr = irast.Array(elements=elements, typeref=typeref) return ensure_set(arr, type_override=stype, ctx=ctx)
def new_array_set( elements: Sequence[irast.Base], *, ctx: context.ContextLevel, srcctx: Optional[parsing.ParserContext]=None) -> irast.Set: arr = irast.Array(elements=elements) if elements: stype = inference.infer_type(arr, env=ctx.env) else: anytype = s_pseudo.Any.create() stype = s_types.Array.from_subtypes(ctx.env.schema, [anytype]) if srcctx is not None: ctx.env.type_origins[anytype] = srcctx typeref = typegen.type_to_typeref(stype, env=ctx.env) arr = irast.Array(elements=elements, typeref=typeref) return ensure_set(arr, type_override=stype, ctx=ctx)
def ql_typeref_to_ir_typeref( ql_t: qlast.TypeExpr, *, ctx: context.ContextLevel) -> typing.Union[irast.Array, irast.TypeRef]: types = _ql_typeexpr_to_ir_typeref(ql_t, ctx=ctx) if len(types) > 1: return irast.Array(elements=types) else: return types[0]
def _cast_array_literal( ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: assert isinstance(ir_set.expr, irast.Array) orig_typeref = typegen.type_to_typeref(orig_stype, env=ctx.env) new_typeref = typegen.type_to_typeref(new_stype, env=ctx.env) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) if direct_cast is None: if not new_stype.is_array(): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) from None assert isinstance(new_stype, s_types.Array) el_type = new_stype.get_subtypes(ctx.env.schema)[0] intermediate_stype = orig_stype else: el_type = new_stype ctx.env.schema, intermediate_stype = s_types.Array.from_subtypes( ctx.env.schema, [el_type]) intermediate_typeref = typegen.type_to_typeref( intermediate_stype, env=ctx.env) casted_els = [] for el in ir_set.expr.elements: el = compile_cast(el, el_type, cardinality_mod=qlast.CardinalityModifier.Required, ctx=ctx, srcctx=srcctx) casted_els.append(el) new_array = setgen.ensure_set( irast.Array(elements=casted_els, typeref=intermediate_typeref), ctx=ctx) if direct_cast is not None: return _cast_to_ir( new_array, direct_cast, intermediate_stype, new_stype, ctx=ctx) else: cast_ir = irast.TypeCast( expr=new_array, from_type=orig_typeref, to_type=new_typeref, sql_cast=True, sql_expr=False, ) return setgen.ensure_set(cast_ir, ctx=ctx)
def _cast_array_literal(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Type, *, srcctx: parsing.ParserContext, ctx: context.ContextLevel) -> irast.Base: orig_typeref = irtyputils.type_to_typeref(ctx.env.schema, orig_stype) new_typeref = irtyputils.type_to_typeref(ctx.env.schema, new_stype) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) if direct_cast is None: if not new_stype.is_array(): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}', context=srcctx) from None el_type = new_stype.get_subtypes(ctx.env.schema)[0] else: el_type = new_stype casted_els = [] for el in ir_set.expr.elements: el = compile_cast(el, el_type, ctx=ctx, srcctx=srcctx) casted_els.append(el) new_array = setgen.ensure_set(irast.Array(elements=casted_els, typeref=orig_typeref), ctx=ctx) if direct_cast is not None: return _cast_to_ir(new_array, direct_cast, orig_stype, new_stype, ctx=ctx) else: cast_ir = irast.TypeCast( expr=new_array, from_type=orig_typeref, to_type=new_typeref, sql_cast=True, ) return setgen.ensure_set(cast_ir, ctx=ctx)
def compile_TypeCast(expr: qlast.TypeCast, *, ctx: context.ContextLevel) -> irast.Set: target_typeref = typegen.ql_typeexpr_to_ir_typeref(expr.type, ctx=ctx) ir_expr: irast.Base if (isinstance(expr.expr, qlast.Array) and not expr.expr.elements and irtyputils.is_array(target_typeref)): ir_expr = irast.Array() elif isinstance(expr.expr, qlast.Parameter): pt = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx) if ((pt.is_tuple(ctx.env.schema) or pt.is_anytuple(ctx.env.schema)) and not ctx.env.options.func_params): raise errors.QueryError( 'cannot pass tuples as query parameters', context=expr.expr.context, ) if (isinstance(pt, s_types.Collection) and pt.contains_array_of_tuples(ctx.env.schema) and not ctx.env.options.func_params): raise errors.QueryError( 'cannot pass collections with tuple elements' ' as query parameters', context=expr.expr.context, ) param_name = expr.expr.name if expr.cardinality_mod: if expr.cardinality_mod == qlast.CardinalityModifier.Optional: required = False elif expr.cardinality_mod == qlast.CardinalityModifier.Required: required = True else: raise NotImplementedError( f"cardinality modifier {expr.cardinality_mod}") else: required = True if ctx.env.options.json_parameters: if param_name.isdecimal(): raise errors.QueryError( 'queries compiled to accept JSON parameters do not ' 'accept positional parameters', context=expr.expr.context) typeref = typegen.type_to_typeref( ctx.env.get_track_schema_type('std::json'), env=ctx.env, ) param = casts.compile_cast( irast.Parameter( typeref=typeref, name=param_name, required=required, context=expr.expr.context, ), pt, srcctx=expr.expr.context, ctx=ctx, ) else: typeref = typegen.type_to_typeref(pt, env=ctx.env) param = setgen.ensure_set( irast.Parameter( typeref=typeref, name=param_name, required=required, context=expr.expr.context, ), ctx=ctx, ) if param_name not in ctx.env.query_parameters: if ctx.env.query_parameters: first_key: str = next(iter(ctx.env.query_parameters)) if first_key.isdecimal(): if not param_name.isdecimal(): raise errors.QueryError( f'cannot combine positional and named parameters ' f'in the same query', context=expr.expr.context) else: if param_name.isdecimal(): raise errors.QueryError(f'expected a named argument', context=expr.expr.context) ctx.env.query_parameters[param_name] = irast.Param( name=param_name, required=required, schema_type=pt, ir_type=typeref, ) else: param_first_type = ctx.env.query_parameters[param_name].schema_type if not param_first_type.explicitly_castable_to(pt, ctx.env.schema): raise errors.QueryError( f'cannot cast ' f'{param_first_type.get_displayname(ctx.env.schema)} to ' f'{pt.get_displayname(ctx.env.schema)}', context=expr.expr.context) return param else: with ctx.new() as subctx: # We use "exposed" mode in case this is a type of a cast # that wants view shapes, e.g. a std::json cast. We do # this wholesale to support tuple and array casts without # having to analyze the target type (which is cumbersome # in QL AST). subctx.expr_exposed = True ir_expr = dispatch.compile(expr.expr, ctx=subctx) new_stype = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx) return casts.compile_cast(ir_expr, new_stype, cardinality_mod=expr.cardinality_mod, ctx=ctx, srcctx=expr.expr.context)
def compile_TypeCast(expr: qlast.TypeCast, *, ctx: context.ContextLevel) -> irast.Set: target_stype = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx) target_typeref = typegen.type_to_typeref(target_stype, env=ctx.env) ir_expr: Union[irast.Set, irast.Expr] if (isinstance(expr.expr, qlast.Array) and not expr.expr.elements and irtyputils.is_array(target_typeref)): ir_expr = irast.Array(elements=[], typeref=target_typeref) elif isinstance(expr.expr, qlast.Parameter): pt = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx) if ((pt.is_tuple(ctx.env.schema) or pt.is_anytuple(ctx.env.schema)) and not ctx.env.options.func_params): raise errors.QueryError( 'cannot pass tuples as query parameters', context=expr.expr.context, ) if (isinstance(pt, s_types.Collection) and pt.contains_array_of_tuples(ctx.env.schema) and not ctx.env.options.func_params): raise errors.QueryError( 'cannot pass collections with tuple elements' ' as query parameters', context=expr.expr.context, ) param_name = expr.expr.name if expr.cardinality_mod: if expr.cardinality_mod == qlast.CardinalityModifier.Optional: required = False elif expr.cardinality_mod == qlast.CardinalityModifier.Required: required = True else: raise NotImplementedError( f"cardinality modifier {expr.cardinality_mod}") else: required = True if ctx.env.options.json_parameters: if param_name.isdecimal(): raise errors.QueryError( 'queries compiled to accept JSON parameters do not ' 'accept positional parameters', context=expr.expr.context) typeref = typegen.type_to_typeref( ctx.env.get_track_schema_type(sn.QualName('std', 'json')), env=ctx.env, ) param = casts.compile_cast( irast.Parameter( typeref=typeref, name=param_name, required=required, context=expr.expr.context, ), pt, srcctx=expr.expr.context, ctx=ctx, ) else: typeref = typegen.type_to_typeref(pt, env=ctx.env) param = setgen.ensure_set( irast.Parameter( typeref=typeref, name=param_name, required=required, context=expr.expr.context, ), ctx=ctx, ) if param_name not in ctx.env.query_parameters: if ctx.env.query_parameters: first_key: str = next(iter(ctx.env.query_parameters)) if first_key.isdecimal(): if not param_name.isdecimal(): raise errors.QueryError( f'cannot combine positional and named parameters ' f'in the same query', context=expr.expr.context) else: if param_name.isdecimal(): raise errors.QueryError(f'expected a named argument', context=expr.expr.context) ctx.env.query_parameters[param_name] = irast.Param( name=param_name, required=required, schema_type=pt, ir_type=typeref, ) else: param_first_type = ctx.env.query_parameters[param_name].schema_type if not param_first_type.castable_to(pt, ctx.env.schema): raise errors.QueryError( f'cannot cast ' f'{param_first_type.get_displayname(ctx.env.schema)} to ' f'{pt.get_displayname(ctx.env.schema)}', context=expr.expr.context) return param else: with ctx.new() as subctx: if target_stype.contains_json(subctx.env.schema): # JSON wants type shapes and acts as an output sink. subctx.expr_exposed = True subctx.inhibit_implicit_limit = True subctx.implicit_id_in_shapes = False subctx.implicit_tid_in_shapes = False subctx.implicit_tname_in_shapes = False ir_expr = dispatch.compile(expr.expr, ctx=subctx) return casts.compile_cast(ir_expr, target_stype, cardinality_mod=expr.cardinality_mod, ctx=ctx, srcctx=expr.expr.context)
def compile_cast( ir_expr: Union[irast.Set, irast.Expr], new_stype: s_types.Type, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel, cardinality_mod: Optional[qlast.CardinalityModifier] = None ) -> irast.Set: if isinstance(ir_expr, irast.EmptySet): # For the common case of casting an empty set, we simply # generate a new EmptySet node of the requested type. return setgen.new_empty_set( stype=new_stype, alias=ir_expr.path_id.target_name_hint.name, ctx=ctx, srcctx=ir_expr.context) elif irutils.is_untyped_empty_array_expr(ir_expr): # Ditto for empty arrays. new_typeref = typegen.type_to_typeref(new_stype, ctx.env) return setgen.ensure_set(irast.Array(elements=[], typeref=new_typeref), ctx=ctx) ir_set = setgen.ensure_set(ir_expr, ctx=ctx) orig_stype = setgen.get_set_type(ir_set, ctx=ctx) if (orig_stype == new_stype and cardinality_mod is not qlast.CardinalityModifier.Required): return ir_set elif orig_stype.is_object_type() and new_stype.is_object_type(): # Object types cannot be cast between themselves, # as cast is a _constructor_ operation, and the only # valid way to construct an object is to INSERT it. raise errors.QueryError( f'cannot cast object type ' f'{orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}, use ' f'`...[IS {new_stype.get_displayname(ctx.env.schema)}]` instead', context=srcctx) if isinstance(ir_set.expr, irast.Array): return _cast_array_literal(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) elif orig_stype.is_tuple(ctx.env.schema): return _cast_tuple(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) elif orig_stype.issubclass(ctx.env.schema, new_stype): # The new type is a supertype of the old type, # and is always a wider domain, so we simply reassign # the stype. return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, cardinality_mod=cardinality_mod, ctx=ctx) elif new_stype.issubclass(ctx.env.schema, orig_stype): # The new type is a subtype, so may potentially have # a more restrictive domain, generate a cast call. return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, cardinality_mod=cardinality_mod, ctx=ctx) elif orig_stype.is_array(): return _cast_array(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) else: json_t = ctx.env.get_track_schema_type('std::json') if (new_stype.issubclass(ctx.env.schema, json_t) and ir_set.path_id.is_objtype_path()): # JSON casts of objects are special: we want the full shape # and not just an identity. with ctx.new() as subctx: subctx.implicit_id_in_shapes = False subctx.implicit_tid_in_shapes = False viewgen.compile_view_shapes(ir_set, ctx=subctx) elif (orig_stype.issubclass(ctx.env.schema, json_t) and new_stype.is_enum(ctx.env.schema)): # Casts from json to enums need some special handling # here, where we have access to the enum type. Just turn # it into json->str and str->enum. str_typ = ctx.env.get_track_schema_type('std::str') str_ir = compile_cast(ir_expr, str_typ, srcctx=srcctx, ctx=ctx) return compile_cast(str_ir, new_stype, cardinality_mod=cardinality_mod, srcctx=srcctx, ctx=ctx) elif (orig_stype.issubclass(ctx.env.schema, json_t) and isinstance(new_stype, s_types.Array) and not new_stype.get_subtypes(ctx.env.schema)[0].issubclass( ctx.env.schema, json_t)): # Turn casts from json->array<T> into json->array<json> # and array<json>->array<T>. ctx.env.schema, json_array_typ = s_types.Array.from_subtypes( ctx.env.schema, [json_t]) json_array_ir = compile_cast(ir_expr, json_array_typ, srcctx=srcctx, ctx=ctx) return compile_cast(json_array_ir, new_stype, cardinality_mod=cardinality_mod, srcctx=srcctx, ctx=ctx) elif (orig_stype.issubclass(ctx.env.schema, json_t) and isinstance(new_stype, s_types.Tuple)): return _cast_json_to_tuple(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) return _compile_cast(ir_expr, orig_stype, new_stype, cardinality_mod=cardinality_mod, srcctx=srcctx, ctx=ctx)
def compile_TypeCast(expr: qlast.TypeCast, *, ctx: context.ContextLevel) -> irast.Set: target_typeref = typegen.ql_typeexpr_to_ir_typeref(expr.type, ctx=ctx) ir_expr: irast.Base if (isinstance(expr.expr, qlast.Array) and not expr.expr.elements and irtyputils.is_array(target_typeref)): ir_expr = irast.Array() elif isinstance(expr.expr, qlast.Parameter): pt = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx) param_name = expr.expr.name if param_name not in ctx.env.query_parameters: if ctx.env.query_parameters: first_key: str = next(iter(ctx.env.query_parameters)) if first_key.isdecimal(): if not param_name.isdecimal(): raise errors.QueryError( f'cannot combine positional and named parameters ' f'in the same query', context=expr.expr.context) else: if param_name.isdecimal(): raise errors.QueryError(f'expected a named argument', context=expr.expr.context) ctx.env.query_parameters[param_name] = pt else: param_first_type = ctx.env.query_parameters[param_name] if not param_first_type.explicitly_castable_to(pt, ctx.env.schema): raise errors.QueryError( f'cannot cast ' f'{param_first_type.get_displayname(ctx.env.schema)} to ' f'{pt.get_displayname(ctx.env.schema)}', context=expr.expr.context) if ctx.env.json_parameters: if param_name.isdecimal(): raise errors.QueryError( 'queries compiled to accept JSON parameters do not ' 'accept positional parameters', context=expr.expr.context) json_typeref = irtyputils.type_to_typeref( ctx.env.schema, ctx.env.get_track_schema_type('std::json')) param = cast.compile_cast( irast.Parameter( typeref=json_typeref, name=param_name, context=expr.expr.context, ), pt, srcctx=expr.expr.context, ctx=ctx, ) else: param = setgen.ensure_set( irast.Parameter( typeref=irtyputils.type_to_typeref(ctx.env.schema, pt), name=param_name, context=expr.expr.context, ), ctx=ctx, ) return param else: with ctx.new() as subctx: # We use "exposed" mode in case this is a type of a cast # that wants view shapes, e.g. a std::json cast. We do # this wholesale to support tuple and array casts without # having to analyze the target type (which is cumbersome # in QL AST). subctx.expr_exposed = True ir_expr = dispatch.compile(expr.expr, ctx=subctx) new_stype = typegen.ql_typeexpr_to_type(expr.type, ctx=ctx) return cast.compile_cast(ir_expr, new_stype, ctx=ctx, srcctx=expr.expr.context)
def compile_cast(ir_expr: irast.Base, new_stype: s_types.Type, *, srcctx: parsing.ParserContext, ctx: context.ContextLevel) -> irast.OperatorCall: if isinstance(ir_expr, irast.EmptySet): # For the common case of casting an empty set, we simply # generate a new EmptySet node of the requested type. return setgen.new_empty_set( stype=new_stype, alias=ir_expr.path_id.target_name_hint.name, ctx=ctx, srcctx=ir_expr.context) elif irutils.is_untyped_empty_array_expr(ir_expr): # Ditto for empty arrays. new_typeref = irtyputils.type_to_typeref(ctx.env.schema, new_stype) return setgen.ensure_set(irast.Array(elements=[], typeref=new_typeref), ctx=ctx) ir_set = setgen.ensure_set(ir_expr, ctx=ctx) orig_stype = setgen.get_set_type(ir_set, ctx=ctx) if orig_stype == new_stype: return ir_set elif orig_stype.is_object_type() and new_stype.is_object_type(): # Object types cannot be cast between themselves, # as cast is a _constructor_ operation, and the only # valid way to construct an object is to INSERT it. raise errors.QueryError( f'cannot cast object type ' f'{orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}, use ' f'`...[IS {new_stype.get_displayname(ctx.env.schema)}]` instead', context=srcctx) if isinstance(ir_set.expr, irast.Array): return _cast_array_literal(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) elif orig_stype.is_tuple(): return _cast_tuple(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) elif orig_stype.issubclass(ctx.env.schema, new_stype): # The new type is a supertype of the old type, # and is always a wider domain, so we simply reassign # the stype. return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, ctx=ctx) elif new_stype.issubclass(ctx.env.schema, orig_stype): # The new type is a subtype, so may potentially have # a more restrictive domain, generate a cast call. return _inheritance_cast_to_ir(ir_set, orig_stype, new_stype, ctx=ctx) elif orig_stype.is_array(): return _cast_array(ir_set, orig_stype, new_stype, srcctx=srcctx, ctx=ctx) else: json_t = ctx.env.get_track_schema_object('std::json') if (new_stype.issubclass(ctx.env.schema, json_t) and ir_set.path_id.is_objtype_path()): # JSON casts of objects are special: we want the full shape # and not just an identity. with ctx.new() as subctx: subctx.implicit_id_in_shapes = False subctx.implicit_tid_in_shapes = False viewgen.compile_view_shapes(ir_set, ctx=subctx) return _compile_cast(ir_expr, orig_stype, new_stype, srcctx=srcctx, ctx=ctx)