blob: 5e4344254204e0e67493f0db1e33249abd65a1ab [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "postgres.h"
#include "nodes/extensible.h"
#include "optimizer/cypher_createplan.h"
#include "optimizer/cypher_pathnode.h"
#include "parser/cypher_analyze.h"
#include "executor/cypher_utils.h"
#include "optimizer/subselect.h"
#include "nodes/makefuncs.h"
static Const *convert_sublink_to_subplan(PlannerInfo *root,
List *custom_private);
static bool expr_has_sublink(Node *node, void *context);
const CustomPathMethods cypher_create_path_methods = {
CREATE_PATH_NAME, plan_cypher_create_path, NULL};
const CustomPathMethods cypher_set_path_methods = {
SET_PATH_NAME, plan_cypher_set_path, NULL};
const CustomPathMethods cypher_delete_path_methods = {
DELETE_PATH_NAME, plan_cypher_delete_path, NULL};
const CustomPathMethods cypher_merge_path_methods = {
MERGE_PATH_NAME, plan_cypher_merge_path, NULL};
CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel,
List *custom_private)
{
CustomPath *cp;
cp = makeNode(CustomPath);
cp->path.pathtype = T_CustomScan;
cp->path.parent = rel;
cp->path.pathtarget = rel->reltarget;
cp->path.param_info = NULL;
/* Do not allow parallel methods */
cp->path.parallel_aware = false;
cp->path.parallel_safe = false;
cp->path.parallel_workers = 0;
cp->path.rows = 0; /* Basic CREATE will not return rows */
cp->path.startup_cost = 0; /* Basic CREATE will not fetch any pages */
cp->path.total_cost = 0;
/* No output ordering for basic CREATE */
cp->path.pathkeys = NULL;
/* Disable all custom flags for now */
cp->flags = 0;
cp->custom_paths = rel->pathlist;
cp->custom_private = custom_private;
cp->methods = &cypher_create_path_methods;
return cp;
}
CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel,
List *custom_private)
{
CustomPath *cp;
cp = makeNode(CustomPath);
cp->path.pathtype = T_CustomScan;
cp->path.parent = rel;
cp->path.pathtarget = rel->reltarget;
cp->path.param_info = NULL;
/* Do not allow parallel methods */
cp->path.parallel_aware = false;
cp->path.parallel_safe = false;
cp->path.parallel_workers = 0;
cp->path.rows = 0; /* Basic SET will not return rows */
cp->path.startup_cost = 0; /* Basic SET will not fetch any pages */
cp->path.total_cost = 0;
/* No output ordering for basic SET */
cp->path.pathkeys = NULL;
/* Disable all custom flags for now */
cp->flags = 0;
cp->custom_paths = rel->pathlist;
cp->custom_private = custom_private;
cp->methods = &cypher_set_path_methods;
return cp;
}
/*
* Creates a Delete Path. Makes the original path a child of the new
* path. We leave it to the caller to replace the pathlist of the rel.
*/
CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel,
List *custom_private)
{
CustomPath *cp;
cp = makeNode(CustomPath);
cp->path.pathtype = T_CustomScan;
cp->path.parent = rel;
cp->path.pathtarget = rel->reltarget;
cp->path.param_info = NULL;
/* Do not allow parallel methods */
cp->path.parallel_aware = false;
cp->path.parallel_safe = false;
cp->path.parallel_workers = 0;
cp->path.rows = 0;
cp->path.startup_cost = 0;
cp->path.total_cost = 0;
/* No output ordering for basic SET */
cp->path.pathkeys = NULL;
/* Disable all custom flags for now */
cp->flags = 0;
/* Make the original paths the children of the new path */
cp->custom_paths = rel->pathlist;
/* Store the metadata Delete will need in the execution phase. */
cp->custom_private = custom_private;
/* Tells Postgres how to turn this path to the correct CustomScan */
cp->methods = &cypher_delete_path_methods;
return cp;
}
/*
* Creates a merge path. Makes the original path a child of the new
* path. We leave it to the caller to replace the pathlist of the rel.
*/
CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel,
List *custom_private)
{
CustomPath *cp;
cp = makeNode(CustomPath);
cp->path.pathtype = T_CustomScan;
cp->path.parent = rel;
cp->path.pathtarget = rel->reltarget;
cp->path.param_info = NULL;
/* Do not allow parallel methods */
cp->path.parallel_aware = false;
cp->path.parallel_safe = false;
cp->path.parallel_workers = 0;
cp->path.rows = 0;
cp->path.startup_cost = 0;
cp->path.total_cost = 0;
/* No output ordering for basic SET */
cp->path.pathkeys = NULL;
/* Disable all custom flags for now */
cp->flags = 0;
/* Make the original paths the children of the new path */
cp->custom_paths = rel->pathlist;
/*
* Store the metadata Merge will need in the execution phase.
* We may have a sublink here in case the user used a list
* comprehension in merge.
*/
if (rel->subroot->parse->hasSubLinks)
{
cp->custom_private = list_make1(convert_sublink_to_subplan(root, custom_private));
}
else
{
cp->custom_private = custom_private;
}
/* Tells Postgres how to turn this path to the correct CustomScan */
cp->methods = &cypher_merge_path_methods;
return cp;
}
/*
* Deserializes the merge information and checks if any property
* expression (prop_expr) contains a SubLink.
* If found, converts the SubLink to a SubPlan, updates the
* structure accordingly, and serializes it back.
*/
static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private)
{
cypher_merge_information *merge_information;
char *serialized_data = NULL;
Const *c = NULL;
ListCell *lc = NULL;
StringInfo str = makeStringInfo();
c = linitial(custom_private);
serialized_data = (char *)c->constvalue;
merge_information = stringToNode(serialized_data);
Assert(is_ag_node(merge_information, cypher_merge_information));
/* Only part where we can expect a sublink is in prop_expr. */
foreach (lc, merge_information->path->target_nodes)
{
cypher_target_node *node = (cypher_target_node *)lfirst(lc);
Node *prop_expr = (Node *) node->prop_expr;
if (expr_has_sublink(prop_expr, NULL))
{
node->prop_expr = (Expr *) SS_process_sublinks(root, prop_expr, false);
}
}
/* Serialize the information again and return it. */
outNode(str, (Node *)merge_information);
return makeConst(INTERNALOID, -1, InvalidOid, str->len,
PointerGetDatum(str->data), false, false);
}
/*
* Helper function to check if the node has a sublink.
*/
static bool expr_has_sublink(Node *node, void *context)
{
if (node == NULL)
{
return false;
}
if (IsA(node, SubLink))
{
return true;
}
return cypher_expr_tree_walker(node, expr_has_sublink, context);
}