def insert_value_for_shape_element( insert_stmt: pgast.InsertStmt, wrapper: pgast.Query, ir_stmt: irast.MutatingStmt, shape_el: irast.Set, iterator_id: pgast.OutputVar, *, ptr_info: pg_types.PointerStorageInfo, ctx: context.CompilerContextLevel) -> pgast.OutputVar: rel = compile_insert_shape_element(insert_stmt, wrapper, ir_stmt, shape_el, iterator_id, ctx=ctx) insvalue = pathctx.get_path_value_var(rel, shape_el.path_id, env=ctx.env) if isinstance(insvalue, pgast.TupleVar): for element in insvalue.elements: name = element.path_id.rptr_name(ctx.env.schema) if name == 'std::target': insvalue = pathctx.get_path_value_var(rel, element.path_id, env=ctx.env) break else: raise RuntimeError('could not find std::target in ' 'insert computable') insvalue = pgast.TypeCast( arg=insvalue, type_name=pgast.TypeName(name=ptr_info.column_type), ) return insvalue
def compile_TypeCast( expr: irast.TypeCast, *, ctx: context.CompilerContextLevel) -> pgast.Base: pg_expr = dispatch.compile(expr.expr, ctx=ctx) if expr.sql_cast: # Use explicit SQL cast. to_type = irutils.typeref_to_type(ctx.env.schema, expr.to_type) pg_type = pg_types.pg_type_from_object(ctx.env.schema, to_type) return pgast.TypeCast( arg=pg_expr, type_name=pgast.TypeName( name=pg_type ) ) elif expr.sql_function or expr.sql_expr: # Cast implemented as a function. if expr.sql_expr: func_name = common.get_backend_cast_name( expr.cast_name, aspect='function') else: func_name = (expr.sql_function,) return pgast.FuncCall( name=func_name, args=[pg_expr], ) else: raise RuntimeError('cast not supported')
def unnamed_tuple_as_json_object(expr, *, stype, env): assert stype.is_tuple() and not stype.named vals = [] subtypes = stype.get_subtypes() for el_idx, el_type in enumerate(subtypes): type_sentinel = pgast.TypeCast( arg=pgast.NullConstant(), type_name=pgast.TypeName( name=pgtypes.pg_type_from_object(env.schema, el_type))) val = pgast.FuncCall(name=('edgedb', 'row_getattr_by_num'), args=[ expr, pgast.NumericConstant(val=str(el_idx + 1)), type_sentinel ]) if el_type.is_collection(): val = coll_as_json_object(val, stype=el_type, env=env) vals.append(val) return pgast.FuncCall(name=( 'edgedb', 'row_to_jsonb_array', ), args=[expr], null_safe=True, ser_safe=True, nullable=expr.nullable)
def named_tuple_as_json_object(expr, *, stype, env): assert stype.is_tuple() and stype.named keyvals = [] subtypes = stype.iter_subtypes() for el_idx, (el_name, el_type) in enumerate(subtypes): keyvals.append(pgast.StringConstant(val=el_name)) type_sentinel = pgast.TypeCast( arg=pgast.NullConstant(), type_name=pgast.TypeName( name=pgtypes.pg_type_from_object(env.schema, el_type))) val = pgast.FuncCall(name=('edgedb', 'row_getattr_by_num'), args=[ expr, pgast.NumericConstant(val=str(el_idx + 1)), type_sentinel ]) if el_type.is_collection(): val = coll_as_json_object(val, stype=el_type, env=env) keyvals.append(val) return pgast.FuncCall(name=('jsonb_build_object', ), args=keyvals, null_safe=True, ser_safe=True, nullable=expr.nullable)
def array_as_json_object(expr, *, stype, env): if stype.element_type.is_tuple(): coldeflist = [] json_args = [] is_named = stype.element_type.named for n, st in stype.element_type.iter_subtypes(): colname = env.aliases.get(str(n)) if is_named: json_args.append(pgast.StringConstant(val=n)) val = pgast.ColumnRef(name=[colname]) if st.is_collection(): val = coll_as_json_object(val, stype=st, env=env) json_args.append(val) coldeflist.append( pgast.ColumnDef( name=colname, typename=pgast.TypeName( name=pgtypes.pg_type_from_object(env.schema, st)))) if is_named: json_func = 'jsonb_build_object' else: json_func = 'jsonb_build_array' return pgast.SelectStmt(target_list=[ pgast.ResTarget( val=pgast.FuncCall(name=('jsonb_agg', ), args=[ pgast.FuncCall( name=(json_func, ), args=json_args, ) ]), ser_safe=True, ) ], from_clause=[ pgast.RangeFunction( alias=pgast.Alias( aliasname=env.aliases.get('q'), ), coldeflist=coldeflist, functions=[ pgast.FuncCall( name=('unnest', ), args=[expr], ) ]) ]) else: return pgast.FuncCall(name=('to_jsonb', ), args=[expr], null_safe=True, ser_safe=True)
def type_node(typename): typename = list(typename) if typename[-1].endswith('[]'): # array typename[-1] = typename[-1][:-2] array_bounds = [-1] else: array_bounds = [] return pgast.TypeName(name=tuple(typename), array_bounds=array_bounds)
def compile_BooleanConstant( expr: irast.BooleanConstant, *, ctx: context.CompilerContextLevel) -> pgast.Base: return pgast.TypeCast( arg=pgast.BooleanConstant(val=expr.value), type_name=pgast.TypeName( name=pg_types.pg_type_from_object( ctx.env.schema, expr.stype) ) )
def compile_FunctionCall( expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: if expr.typemod is ql_ft.TypeModifier.SET_OF: raise RuntimeError( 'set returning functions are not supported in simple expressions') args = [dispatch.compile(a, ctx=ctx) for a in expr.args] if expr.has_empty_variadic: var = pgast.TypeCast( arg=pgast.ArrayExpr(elements=[]), type_name=pgast.TypeName( name=pg_types.pg_type_from_object( ctx.env.schema, expr.variadic_param_type) ) ) args.append(pgast.VariadicArgument(expr=var)) if expr.func_sql_function: name = (expr.func_sql_function,) else: name = common.schema_name_to_pg_name(expr.func_shortname) result = pgast.FuncCall(name=name, args=args) if expr.force_return_cast: # The underlying function has a return value type # different from that of the EdgeQL function declaration, # so we need to make an explicit cast here. result = pgast.TypeCast( arg=result, type_name=pgast.TypeName( name=pg_types.pg_type_from_object( ctx.env.schema, expr.stype) ) ) return result
def compile_SliceIndirection( expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: # Handle Expr[Index], where Expr may be std::str, array<T> or # std::json. For strings we translate this into substr calls. # Arrays use the native slice syntax. JSON is handled by a # combination of unnesting aggregation and array slicing. with ctx.new() as subctx: subctx.expr_exposed = False subj = dispatch.compile(expr.expr, ctx=subctx) if expr.start is None: start = pgast.NullConstant() else: start = dispatch.compile(expr.start, ctx=subctx) if expr.stop is None: stop = pgast.NullConstant() else: stop = dispatch.compile(expr.stop, ctx=subctx) # any integer indexes must be upcast into int to fit the helper # function signature start = pgast.TypeCast( arg=start, type_name=pgast.TypeName( name=('int',) ) ) stop = pgast.TypeCast( arg=stop, type_name=pgast.TypeName( name=('int',) ) ) result = pgast.FuncCall( name=('edgedb', '_slice'), args=[subj, start, stop] ) return result
def compile_Array( expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: elements = [dispatch.compile(e, ctx=ctx) for e in expr.elements] array = astutils.safe_array_expr(elements) if irutils.is_empty_array_expr(expr): return pgast.TypeCast( arg=array, type_name=pgast.TypeName( name=pg_types.pg_type_from_object( ctx.env.schema, expr.stype) ) ) else: return array
def compile_IndexIndirection(expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: # Handle Expr[Index], where Expr may be std::str or array<T>. # For strings we translate this into substr calls, whereas # for arrays the native slice syntax is used. is_string = False arg_type = _infer_type(expr.expr, ctx=ctx) with ctx.new() as subctx: subctx.expr_exposed = False subj = dispatch.compile(expr.expr, ctx=subctx) index = dispatch.compile(expr.index, ctx=subctx) if isinstance(arg_type, s_scalars.ScalarType): b = arg_type.get_topmost_concrete_base() is_string = b.name == 'std::str' one = pgast.Constant(val=1) zero = pgast.Constant(val=0) when_cond = astutils.new_binop(lexpr=index, rexpr=zero, op=ast.ops.LT) index_plus_one = astutils.new_binop(lexpr=index, op=ast.ops.ADD, rexpr=one) if is_string: upper_bound = pgast.FuncCall(name=('char_length', ), args=[subj]) else: upper_bound = pgast.FuncCall(name=('array_upper', ), args=[subj, one]) neg_off = astutils.new_binop(lexpr=upper_bound, rexpr=index_plus_one, op=ast.ops.ADD) when_expr = pgast.CaseWhen(expr=when_cond, result=neg_off) index = pgast.CaseExpr(args=[when_expr], defresult=index_plus_one) if is_string: index = pgast.TypeCast(arg=index, type_name=pgast.TypeName(name=('int', ))) result = pgast.FuncCall(name=('substr', ), args=[subj, index, one]) else: indirection = pgast.Indices(ridx=index) result = pgast.Indirection(arg=subj, indirection=[indirection]) return result
def compile_IndexIndirection( expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: # Handle Expr[Index], where Expr may be std::str, array<T> or # std::json. For strings we translate this into substr calls. # Arrays use the native index access. JSON is handled by using the # `->` accessor. Additionally, in all of the above cases a # boundary-check is performed on the index and an exception is # potentially raised. # line, column and filename are captured here to be used with the # error message srcctx = pgast.StringConstant( val=irutils.get_source_context_as_json(expr.index, errors.InvalidValueError)) with ctx.new() as subctx: subctx.expr_exposed = False subj = dispatch.compile(expr.expr, ctx=subctx) index = dispatch.compile(expr.index, ctx=subctx) # If the index is some integer, cast it into int, because there's # no backend function that handles indexes larger than int. index_t = expr.index.stype int_t = ctx.env.schema.get('std::anyint') if index_t.issubclass(ctx.env.schema, int_t): index = pgast.TypeCast( arg=index, type_name=pgast.TypeName( name=('int',) ) ) result = pgast.FuncCall( name=('edgedb', '_index'), args=[subj, index, srcctx] ) return result
def compile_Parameter( expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: if expr.name.isdecimal(): index = int(expr.name) + 1 result = pgast.ParamRef(number=index) else: if ctx.env.use_named_params: result = pgast.NamedParamRef(name=expr.name) else: if expr.name in ctx.argmap: index = ctx.argmap[expr.name] else: index = len(ctx.argmap) + 1 ctx.argmap[expr.name] = index result = pgast.ParamRef(number=index) return pgast.TypeCast( arg=result, type_name=pgast.TypeName( name=pg_types.pg_type_from_object( ctx.env.schema, expr.stype) ) )
def cast(node: pgast.Base, *, source_type: s_obj.Object, target_type: s_obj.Object, force: bool = False, env: context.Environment) -> pgast.Base: if source_type.name == target_type.name and not force: return node schema = env.schema real_t = schema.get('std::anyreal') int_t = schema.get('std::anyint') json_t = schema.get('std::json') str_t = schema.get('std::str') datetime_t = schema.get('std::datetime') bool_t = schema.get('std::bool') if isinstance(target_type, s_types.Collection): if target_type.schema_name == 'array': if source_type.issubclass(json_t): # If we are casting a jsonb array to array, we do the # following transformation: # EdgeQL: <array<T>>MAP_VALUE # SQL: # SELECT array_agg(j::T) # FROM jsonb_array_elements(MAP_VALUE) AS j inner_cast = cast(pgast.ColumnRef(name=['j']), source_type=source_type, target_type=target_type.element_type, env=env) return pgast.SelectStmt( target_list=[ pgast.ResTarget(val=pgast.FuncCall( name=('array_agg', ), args=[inner_cast])) ], from_clause=[ pgast.RangeFunction(functions=[ pgast.FuncCall(name=('jsonb_array_elements', ), args=[node]) ], alias=pgast.Alias(aliasname='j')) ]) else: # EdgeQL: <array<int64>>['1', '2'] # to SQL: ARRAY['1', '2']::int[] elem_pgtype = pg_types.pg_type_from_object( schema, target_type.element_type, topbase=True) return pgast.TypeCast(arg=node, type_name=pgast.TypeName( name=elem_pgtype, array_bounds=[-1])) else: # `target_type` is not a collection. if (source_type.issubclass(datetime_t) and target_type.issubclass(str_t)): # Normalize datetime to text conversion to have the same # format as one would get by serializing to JSON. # # EdgeQL: <text><datetime>'2010-10-10'; # To SQL: trim(to_json('2010-01-01'::timestamptz)::text, '"') return pgast.FuncCall( name=('trim', ), args=[ pgast.TypeCast(arg=pgast.FuncCall(name=('to_json', ), args=[node]), type_name=pgast.TypeName(name=('text', ))), pgast.Constant(val='"') ]) elif source_type.issubclass(bool_t) and target_type.issubclass(int_t): # PostgreSQL 9.6 doesn't allow to cast 'boolean' to any integer # other than int32: # SELECT 'true'::boolean::bigint; # ERROR: cannot cast type boolean to bigint # So we transform EdgeQL: <int64>BOOL # to SQL: BOOL::int::<targetint> return pgast.TypeCast( arg=pgast.TypeCast(arg=node, type_name=pgast.TypeName(name=('int', ))), type_name=pgast.TypeName( name=pg_types.pg_type_from_scalar(schema, target_type))) elif source_type.issubclass(int_t) and target_type.issubclass(bool_t): # PostgreSQL 9.6 doesn't allow to cast any integer other # than int32 to 'boolean': # SELECT 1::bigint::boolean; # ERROR: cannot cast type bigint to boolea # So we transform EdgeQL: <boolean>INT # to SQL: (INT != 0) return astutils.new_binop(node, pgast.Constant(val=0), op=ast.ops.NE) elif source_type.issubclass(json_t): if (target_type.issubclass(real_t) or target_type.issubclass(bool_t)): # Simply cast to text and the to the target type. return cast(cast(node, source_type=source_type, target_type=str_t, env=env), source_type=str_t, target_type=target_type, env=env) elif target_type.issubclass(str_t): # It's not possible to cast jsonb string to text directly, # so we do a trick: # EdgeQL: <str>JSONB_VAL # SQL: array_to_json(ARRAY[JSONB_VAL])->>0 return astutils.new_binop(pgast.FuncCall( name=('array_to_json', ), args=[pgast.ArrayExpr(elements=[node])]), pgast.Constant(val=0), op='->>') elif target_type.issubclass(json_t): return pgast.TypeCast( arg=node, type_name=pgast.TypeName(name=('jsonb', ))) else: const_type = pg_types.pg_type_from_object(schema, target_type, topbase=True) return pgast.TypeCast(arg=node, type_name=pgast.TypeName(name=const_type)) raise RuntimeError( f'could not cast {source_type.name} to {target_type.name}')
def compile_OperatorCall( expr: irast.OperatorCall, *, ctx: context.CompilerContextLevel) -> pgast.Expr: if expr.typemod is ql_ft.TypeModifier.SET_OF: raise RuntimeError( f'set returning operator {expr.func_shortname!r} is not supported ' f'in simple expressions') args = [dispatch.compile(a, ctx=ctx) for a in expr.args] if expr.operator_kind is ql_ft.OperatorKind.INFIX: lexpr, rexpr = args elif expr.operator_kind is ql_ft.OperatorKind.PREFIX: rexpr = args[0] lexpr = None elif expr.operator_kind is ql_ft.OperatorKind.POSTFIX: lexpr = args[0] rexpr = None else: raise RuntimeError(f'unexpected operator kind: {expr.operator_kind!r}') if expr.sql_operator: sql_oper = expr.sql_operator[0] if len(expr.sql_operator) > 1: # Explicit operand types given in FROM SQL OPERATOR if lexpr is not None: lexpr = pgast.TypeCast( arg=lexpr, type_name=pgast.TypeName( name=(expr.sql_operator[1],) ) ) if rexpr is not None: rexpr = pgast.TypeCast( arg=rexpr, type_name=pgast.TypeName( name=(expr.sql_operator[2],) ) ) else: sql_oper = common.get_backend_operator_name(expr.func_shortname)[1] result = pgast.Expr( kind=pgast.ExprKind.OP, name=sql_oper, lexpr=lexpr, rexpr=rexpr, ) if expr.force_return_cast: # The underlying operator has a return value type # different from that of the EdgeQL operator declaration, # so we need to make an explicit cast here. result = pgast.TypeCast( arg=result, type_name=pgast.TypeName( name=pg_types.pg_type_from_object( ctx.env.schema, expr.stype) ) ) return result
def compile_BinOp(expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: with ctx.new() as newctx: newctx.expr_exposed = False op = expr.op is_bool_op = op in {ast.ops.AND, ast.ops.OR} left = dispatch.compile(expr.left, ctx=newctx) right = dispatch.compile(expr.right, ctx=newctx) if not isinstance(expr.left, irast.EmptySet): left_type = _infer_type(expr.left, ctx=ctx) else: left_type = None if not isinstance(expr.right, irast.EmptySet): right_type = _infer_type(expr.right, ctx=ctx) else: right_type = None if (not isinstance(expr.left, irast.EmptySet) and not isinstance(expr.right, irast.EmptySet)): left_pg_type = pg_types.pg_type_from_object(ctx.env.schema, left_type, True) right_pg_type = pg_types.pg_type_from_object(ctx.env.schema, right_type, True) if (left_pg_type in {('text', ), ('varchar', )} and right_pg_type in {('text', ), ('varchar', )} and op == ast.ops.ADD): op = '||' if isinstance(left_type, s_types.Tuple): left = _tuple_to_row_expr(expr.left, ctx=newctx) left_count = len(left.args) else: left_count = 0 if isinstance(right_type, s_types.Tuple): right = _tuple_to_row_expr(expr.right, ctx=newctx) right_count = len(right.args) else: right_count = 0 if left_count != right_count: # Postgres does not allow comparing rows with # unequal number of entries, but we want to allow # this. Fortunately, we know that such comparison is # always False. result = pgast.Constant(val=False) else: if is_bool_op: # Transform logical operators to force # the correct behaviour with respect to NULLs. # See the OrFilterFunction comment for details. if ctx.clause == 'where': if expr.op == ast.ops.OR: result = pgast.FuncCall(name=('edgedb', '_or'), args=[left, right]) else: # For the purposes of the WHERE clause, # AND operator works correctly, as # it will either return NULL or FALSE, # which both will disqualify the row. result = astutils.new_binop(left, right, op=op) else: # For expressions outside WHERE, we # always want the result to be NULL # if either operand is NULL. bitop = '&' if expr.op == ast.ops.AND else '|' bitcond = astutils.new_binop( lexpr=pgast.TypeCast( arg=left, type_name=pgast.TypeName(name=('int', ))), rexpr=pgast.TypeCast( arg=right, type_name=pgast.TypeName(name=('int', ))), op=bitop) bitcond = pgast.TypeCast( arg=bitcond, type_name=pgast.TypeName(name=('bool', ))) result = bitcond else: result = astutils.new_binop(left, right, op=op) return result
def compile_SliceIndirection(expr: irast.Base, *, ctx: context.CompilerContextLevel) -> pgast.Base: # Handle Expr[Start:End], where Expr may be std::str or array<T>. # For strings we translate this into substr calls, whereas # for arrays the native slice syntax is used. with ctx.new() as subctx: subctx.expr_exposed = False subj = dispatch.compile(expr.expr, ctx=subctx) start = dispatch.compile(expr.start, ctx=subctx) stop = dispatch.compile(expr.stop, ctx=subctx) one = pgast.Constant(val=1) zero = pgast.Constant(val=0) is_string = False arg_type = _infer_type(expr.expr, ctx=ctx) if isinstance(arg_type, s_scalars.ScalarType): b = arg_type.get_topmost_concrete_base() is_string = b.name == 'std::str' if is_string: upper_bound = pgast.FuncCall(name=('char_length', ), args=[subj]) else: upper_bound = pgast.FuncCall(name=('array_upper', ), args=[subj, one]) if astutils.is_null_const(start): lower = one else: lower = start when_cond = astutils.new_binop(lexpr=lower, rexpr=zero, op=ast.ops.LT) lower_plus_one = astutils.new_binop(lexpr=lower, rexpr=one, op=ast.ops.ADD) neg_off = astutils.new_binop(lexpr=upper_bound, rexpr=lower_plus_one, op=ast.ops.ADD) when_expr = pgast.CaseWhen(expr=when_cond, result=neg_off) lower = pgast.CaseExpr(args=[when_expr], defresult=lower_plus_one) if astutils.is_null_const(stop): upper = upper_bound else: upper = stop when_cond = astutils.new_binop(lexpr=upper, rexpr=zero, op=ast.ops.LT) neg_off = astutils.new_binop(lexpr=upper_bound, rexpr=upper, op=ast.ops.ADD) when_expr = pgast.CaseWhen(expr=when_cond, result=neg_off) upper = pgast.CaseExpr(args=[when_expr], defresult=upper) if is_string: lower = pgast.TypeCast(arg=lower, type_name=pgast.TypeName(name=('int', ))) args = [subj, lower] if upper is not upper_bound: for_length = astutils.new_binop(lexpr=upper, op=ast.ops.SUB, rexpr=lower) for_length = astutils.new_binop(lexpr=for_length, op=ast.ops.ADD, rexpr=one) for_length = pgast.TypeCast( arg=for_length, type_name=pgast.TypeName(name=('int', ))) args.append(for_length) result = pgast.FuncCall(name=('substr', ), args=args) else: indirection = pgast.Indices(lidx=lower, ridx=upper) result = pgast.Indirection(arg=subj, indirection=[indirection]) return result
def process_update_body(ir_stmt: irast.MutatingStmt, wrapper: pgast.Query, update_cte: pgast.CommonTableExpr, range_cte: pgast.CommonTableExpr, *, ctx: context.CompilerContextLevel): """Generate SQL DML CTEs from an UpdateStmt IR. :param ir_stmt: IR of the statement. :param wrapper: Top-level SQL query. :param update_cte: CTE representing the SQL UPDATE to the main relation of the Object. :param range_cte: CTE representing the range affected by the statement. """ update_stmt = update_cte.query external_updates = [] toplevel = ctx.toplevel_stmt toplevel.ctes.append(range_cte) toplevel.ctes.append(update_cte) with ctx.newscope() as subctx: # It is necessary to process the expressions in # the UpdateStmt shape body in the context of the # UPDATE statement so that references to the current # values of the updated object are resolved correctly. subctx.path_scope[ir_stmt.subject.path_id] = update_stmt subctx.rel = update_stmt subctx.expr_exposed = False for shape_el in ir_stmt.subject.shape: with subctx.newscope() as scopectx: ptrcls = shape_el.rptr.ptrcls updvalue = shape_el.expr ptr_info = pg_types.get_pointer_storage_info( ptrcls, schema=scopectx.env.schema, resolve_type=True, link_bias=False) # First, process all internal link updates if ptr_info.table_type == 'ObjectType': updvalue = pgast.TypeCast( arg=dispatch.compile(updvalue, ctx=scopectx), type_name=pgast.TypeName(name=ptr_info.column_type)) update_stmt.targets.append( pgast.UpdateTarget(name=ptr_info.column_name, val=updvalue)) props_only = is_props_only_update(shape_el, ctx=scopectx) ptr_info = pg_types.get_pointer_storage_info(ptrcls, resolve_type=False, link_bias=True, schema=ctx.env.schema) if ptr_info and ptr_info.table_type == 'link': external_updates.append((shape_el, props_only)) if not update_stmt.targets: # No updates directly to the set target table, # so convert the UPDATE statement into a SELECT. update_cte.query = pgast.SelectStmt( ctes=update_stmt.ctes, target_list=update_stmt.returning_list, from_clause=[update_stmt.relation] + update_stmt.from_clause, where_clause=update_stmt.where_clause, path_namespace=update_stmt.path_namespace, path_outputs=update_stmt.path_outputs, path_scope=update_stmt.path_scope, path_rvar_map=update_stmt.path_rvar_map.copy(), view_path_id_map=update_stmt.view_path_id_map.copy(), ptr_join_map=update_stmt.ptr_join_map.copy(), ) # Process necessary updates to the link tables. for expr, props_only in external_updates: if props_only: process_linkprop_update(ir_stmt, expr, wrapper, update_cte, ctx=ctx) else: process_link_update(ir_stmt, expr, False, wrapper, update_cte, None, ctx=ctx)
def _get_rel_path_output(rel: pgast.BaseRelation, path_id: irast.PathId, *, aspect: str, ptr_info: typing.Optional[ pg_types.PointerStorageInfo] = None, env: context.Environment) -> pgast.OutputVar: if path_id.is_objtype_path(): if aspect == 'identity': aspect = 'value' if aspect != 'value': raise LookupError( f'invalid request for non-scalar path {path_id} {aspect}') if (path_id == rel.path_id or (rel.path_id.is_type_indirection_path(env.schema) and path_id == rel.path_id.src_path())): path_id = irutils.get_id_path_id(path_id, schema=env.schema) else: if aspect == 'identity': raise LookupError( f'invalid request for scalar path {path_id} {aspect}') elif aspect == 'serialized': aspect = 'value' var = rel.path_outputs.get((path_id, aspect)) if var is not None: return var ptrcls = path_id.rptr() rptr_dir = path_id.rptr_dir() if (rptr_dir is not None and rptr_dir != s_pointers.PointerDirection.Outbound): raise LookupError( f'{path_id} is an inbound pointer and cannot be resolved ' f'on a base relation') if isinstance(rel, pgast.NullRelation): if ptrcls is not None: target = ptrcls.get_target(env.schema) else: target = path_id.target if ptr_info is not None: name = ptr_info.column_name else: name = env.aliases.get('v') val = pgast.TypeCast( arg=pgast.NullConstant(), type_name=pgast.TypeName( name=pg_types.pg_type_from_object(env.schema, target))) rel.target_list.append(pgast.ResTarget(name=name, val=val)) result = pgast.ColumnRef(name=[name], nullable=True) else: if ptrcls is None: raise ValueError( f'could not resolve trailing pointer class for {path_id}') ptr_info = pg_types.get_pointer_storage_info(ptrcls, resolve_type=False, link_bias=False, schema=env.schema) result = pgast.ColumnRef(name=[ptr_info.column_name], nullable=not ptrcls.get_required(env.schema)) rel.path_outputs[path_id, aspect] = result return result