def new_tuple_set(elements: List[irast.TupleElement], *, named: bool, ctx: context.ContextLevel) -> irast.Set: tup = irast.Tuple(elements=elements, named=named) stype = inference.infer_type(tup, env=ctx.env) result_path_id = pathctx.get_expression_path_id(stype, ctx=ctx) final_elems = [] for elem in elements: elem_path_id = pathctx.get_tuple_indirection_path_id(result_path_id, elem.name, get_set_type( elem.val, ctx=ctx), ctx=ctx) final_elems.append( irast.TupleElement( name=elem.name, val=elem.val, path_id=elem_path_id, )) typeref = typegen.type_to_typeref(stype, env=ctx.env) final_tup = irast.Tuple(elements=final_elems, named=named, typeref=typeref) return ensure_set(final_tup, path_id=result_path_id, type_override=stype, ctx=ctx)
def _cast_json_to_tuple( ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Tuple, cardinality_mod: Optional[qlast.CardinalityModifier], *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_path = subctx.create_anchor(ir_set, 'a') # Top-level json->tuple casts should produce an empty set on # null inputs, but error on missing fields or null # subelements, so filter out json nulls directly here to # distinguish those cases. if cardinality_mod != qlast.CardinalityModifier.Required: pathctx.register_set_in_scope(ir_set, ctx=subctx) check = qlast.FunctionCall( func=('__std__', 'json_typeof'), args=[source_path] ) filtered = qlast.SelectQuery( result=source_path, where=qlast.BinOp( left=check, op='!=', right=qlast.StringConstant(value='null'), ) ) filtered_ir = dispatch.compile(filtered, ctx=subctx) source_path = subctx.create_anchor(filtered_ir, 'a') # TODO: try using jsonb_to_record instead of a bunch of # json_get calls and see if that is faster. elements = [] for new_el_name, new_st in new_stype.iter_subtypes(ctx.env.schema): val_e = qlast.FunctionCall( func=('__std__', 'json_get'), args=[ source_path, qlast.StringConstant(value=new_el_name), ], ) val = dispatch.compile(val_e, ctx=subctx) val = compile_cast( val, new_st, cardinality_mod=qlast.CardinalityModifier.Required, ctx=subctx, srcctx=srcctx) elements.append(irast.TupleElement(name=new_el_name, val=val)) return setgen.new_tuple_set( elements, named=new_stype.is_named(ctx.env.schema), ctx=subctx, )
def compile_Tuple(expr: qlast.Base, *, ctx: context.ContextLevel) -> irast.Set: elements = [] for i, el in enumerate(expr.elements): element = irast.TupleElement(name=str(i), val=setgen.ensure_set(dispatch.compile( el, ctx=ctx), ctx=ctx)) elements.append(element) return setgen.new_tuple_set(elements, named=False, ctx=ctx)
def compile_NamedTuple(expr: qlast.NamedTuple, *, ctx: context.ContextLevel) -> irast.Set: elements = [] for el in expr.elements: element = irast.TupleElement(name=el.name.name, val=setgen.ensure_set(dispatch.compile( el.val, ctx=ctx), ctx=ctx)) elements.append(element) return setgen.new_tuple_set(elements, named=True, ctx=ctx)
def compile_NamedTuple(expr: qlast.NamedTuple, *, ctx: context.ContextLevel) -> irast.Set: names = set() elements = [] for el in expr.elements: name = el.name.name if name in names: raise errors.QueryError( f"named tuple has duplicate field '{name}'", context=el.context) names.add(name) element = irast.TupleElement( name=name, val=dispatch.compile(el.val, ctx=ctx), ) elements.append(element) return setgen.new_tuple_set(elements, named=True, ctx=ctx)
def _cast_json_to_tuple(ir_set: irast.Set, orig_stype: s_types.Type, new_stype: s_types.Tuple, *, srcctx: Optional[parsing.ParserContext], ctx: context.ContextLevel) -> irast.Set: with ctx.new() as subctx: subctx.anchors = subctx.anchors.copy() source_alias = subctx.aliases.get('a') subctx.anchors[source_alias] = ir_set # TODO: try using jsonb_to_record instead of a bunch of # json_get calls and see if that is faster. elements = [] for new_el_name, new_st in new_stype.iter_subtypes(ctx.env.schema): val_e = qlast.FunctionCall( func=('__std__', 'json_get'), args=[ qlast.Path(steps=[qlast.ObjectRef(name=source_alias)]), qlast.StringConstant(value=new_el_name), ], ) val = dispatch.compile(val_e, ctx=subctx) val = compile_cast( val, new_st, cardinality_mod=qlast.CardinalityModifier.Required, ctx=subctx, srcctx=srcctx) elements.append(irast.TupleElement(name=new_el_name, val=val)) return setgen.new_tuple_set( elements, named=new_stype.is_named(ctx.env.schema), ctx=subctx, )
def _cast_tuple(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(orig_stype, s_types.Tuple) # Make sure the source tuple expression is pinned in the scope, # so that we don't generate a cross-product of it by evaluating # the tuple indirections. pathctx.register_set_in_scope(ir_set, ctx=ctx) direct_cast = _find_cast(orig_stype, new_stype, srcctx=srcctx, ctx=ctx) orig_subtypes = dict(orig_stype.iter_subtypes(ctx.env.schema)) if direct_cast is not None: # Direct casting to non-tuple involves casting each tuple # element and also keeping the cast around the whole tuple. # This is to trigger the downstream logic of casting # objects (in elements of the tuple). elements = [] for n in orig_subtypes: val = setgen.tuple_indirection_set( ir_set, source=orig_stype, ptr_name=n, ctx=ctx, ) val_type = setgen.get_set_type(val, ctx=ctx) # Element cast val = compile_cast(val, new_stype, ctx=ctx, srcctx=srcctx) elements.append(irast.TupleElement(name=n, val=val)) new_tuple = setgen.new_tuple_set( elements, named=orig_stype.is_named(ctx.env.schema), ctx=ctx, ) return _cast_to_ir(new_tuple, direct_cast, orig_stype, new_stype, ctx=ctx) if not new_stype.is_tuple(ctx.env.schema): 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) assert isinstance(new_stype, s_types.Tuple) new_subtypes = list(new_stype.iter_subtypes(ctx.env.schema)) if len(orig_subtypes) != len(new_subtypes): raise errors.QueryError( f'cannot cast {orig_stype.get_displayname(ctx.env.schema)!r} ' f'to {new_stype.get_displayname(ctx.env.schema)!r}: ' f'the number of elements is not the same', context=srcctx) # For tuple-to-tuple casts we generate a new tuple # to simplify things on sqlgen side. elements = [] for i, n in enumerate(orig_subtypes): val = setgen.tuple_indirection_set( ir_set, source=orig_stype, ptr_name=n, ctx=ctx, ) val_type = setgen.get_set_type(val, ctx=ctx) new_el_name, new_st = new_subtypes[i] if val_type != new_st: # Element cast val = compile_cast(val, new_st, ctx=ctx, srcctx=srcctx) elements.append(irast.TupleElement(name=new_el_name, val=val)) return setgen.new_tuple_set( elements, named=new_stype.is_named(ctx.env.schema), ctx=ctx, )