Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IGNITE-22717 SQL Calcite: User defined SQL views #11467

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/_docs/monitoring-metrics/system-views.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -645,8 +645,9 @@ This view exposes information about SQL views.
[{table_opts}]
|===
|NAME | TYPE | DESCRIPTION
|NAME | string | Name
|SCHEMA | string | Schema
|NAME | string | Name
|SQL | string | SQL query for view
|DESCRIPTION | string | Description
|===

Expand Down
60 changes: 60 additions & 0 deletions docs/_docs/sql-reference/ddl.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,66 @@ DROP INDEX idx_person_name;
----


== CREATE VIEW

Creates an user definded SQL view.


[source,sql]
----
CREATE [OR REPLACE] VIEW [schemaName.]viewName AS query
----

Parameters:

* `schemaName` - the name of the schema, where to create view.
* `viewName` - the name of the view to create.
* `OR REPLACE` - replace view if a view with the specified name already exists.
* `query` - valid SQL query.

Schema changes applied by this command are persisted on disk if link:persistence/native-persistence[Ignite persistence] is enabled. Thus, the changes can survive full cluster restarts.

[discrete]
=== Examples
Create a view:


[source,sql]
----
CREATE VIEW adult AS SELECT * FROM person WHERE age >= 18;
----


== DROP VIEW

Deletes an existing user defined SQL view.


[source,sql]
----
DROP VIEW [IF EXISTS] [schemaName.]viewName
----

Parameters:

* `schemaName` - the schema of the view to drop.
* `viewName` - the name of the view to drop.
* `IF EXISTS` - do not throw an error if a view with the specified name does not exist.

Schema changes applied by this command are persisted on disk if link:persistence/native-persistence[Ignite persistence] is enabled. Thus, the changes can survive full cluster restarts.


[discrete]
=== Examples
Drop a view:


[source,sql]
----
DROP VIEW adult;
----


== CREATE USER

The command creates a user with a given name and password.
Expand Down
6 changes: 4 additions & 2 deletions modules/calcite/src/main/codegen/config.fmpp
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,8 @@ data: {
createStatementParserMethods: [
"SqlCreateTable",
"SqlCreateIndex",
"SqlCreateUser"
"SqlCreateUser",
"SqlCreateView"
]

# List of methods for parsing extensions to "DROP" calls.
Expand All @@ -453,7 +454,8 @@ data: {
dropStatementParserMethods: [
"SqlDropTable",
"SqlDropIndex",
"SqlDropUser"
"SqlDropUser",
"SqlDropView"
]

# List of methods for parsing extensions to "ALTER <scope>" calls.
Expand Down
38 changes: 38 additions & 0 deletions modules/calcite/src/main/codegen/includes/parserImpls.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ SqlCreate SqlCreateTable(Span s, boolean replace) :
final SqlNode query;
}
{
{
if (replace)
throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.unsupportedClause("REPLACE"));
}

<TABLE>
ifNotExists = IfNotExistsOpt()
id = CompoundIdentifier()
Expand Down Expand Up @@ -258,6 +263,11 @@ SqlCreate SqlCreateIndex(Span s, boolean replace) :
SqlNumericLiteral inlineSize = null;
}
{
{
if (replace)
throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.unsupportedClause("REPLACE"));
}

<INDEX>
ifNotExists = IfNotExistsOpt()
[ idxId = SimpleIdentifier() ]
Expand Down Expand Up @@ -412,6 +422,11 @@ SqlCreate SqlCreateUser(Span s, boolean replace) :
final SqlNode password;
}
{
{
if (replace)
throw SqlUtil.newContextException(getPos(), IgniteResource.INSTANCE.unsupportedClause("REPLACE"));
}

<USER> user = SimpleIdentifier()
<WITH> <PASSWORD> password = StringLiteral() {
return new IgniteSqlCreateUser(s.end(this), user, SqlLiteral.unchain(password));
Expand Down Expand Up @@ -752,3 +767,26 @@ SqlNode SqlStatisticsAnalyze():
return new IgniteSqlStatisticsAnalyze(tablesList, optionsList, s.end(this));
}
}

SqlCreate SqlCreateView(Span s, boolean replace) :
{
final SqlIdentifier id;
final SqlNode query;
}
{
<VIEW> id = CompoundIdentifier()
<AS> query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) {
return SqlDdlNodes.createView(s.end(this), replace, id, null, query);
}
}

SqlDrop SqlDropView(Span s, boolean replace) :
{
final boolean ifExists;
final SqlIdentifier id;
}
{
<VIEW> ifExists = IfExistsOpt() id = CompoundIdentifier() {
return SqlDdlNodes.dropView(s.end(this), ifExists, id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.ddl.SqlCreateView;
import org.apache.calcite.sql.ddl.SqlDropView;
import org.apache.calcite.sql.dialect.CalciteSqlDialect;
import org.apache.ignite.cache.QueryIndex;
import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
import org.apache.ignite.internal.processors.query.IgniteSQLException;
Expand Down Expand Up @@ -55,9 +58,11 @@
import org.apache.ignite.internal.sql.command.SqlCommand;
import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand;
import org.apache.ignite.internal.sql.command.SqlCreateUserCommand;
import org.apache.ignite.internal.sql.command.SqlCreateViewCommand;
import org.apache.ignite.internal.sql.command.SqlDropIndexCommand;
import org.apache.ignite.internal.sql.command.SqlDropStatisticsCommand;
import org.apache.ignite.internal.sql.command.SqlDropUserCommand;
import org.apache.ignite.internal.sql.command.SqlDropViewCommand;
import org.apache.ignite.internal.sql.command.SqlIndexColumn;
import org.apache.ignite.internal.sql.command.SqlKillComputeTaskCommand;
import org.apache.ignite.internal.sql.command.SqlKillContinuousQueryCommand;
Expand Down Expand Up @@ -87,7 +92,9 @@ public static boolean isSupported(SqlNode sqlCmd) {
|| sqlCmd instanceof IgniteSqlAlterUser
|| sqlCmd instanceof IgniteSqlDropUser
|| sqlCmd instanceof IgniteSqlKill
|| sqlCmd instanceof IgniteSqlStatisticsCommand;
|| sqlCmd instanceof IgniteSqlStatisticsCommand
|| sqlCmd instanceof SqlCreateView
|| sqlCmd instanceof SqlDropView;
}

/**
Expand Down Expand Up @@ -120,6 +127,10 @@ else if (cmd instanceof IgniteSqlKill)
return convertKill((IgniteSqlKill)cmd, pctx);
else if (cmd instanceof IgniteSqlStatisticsCommand)
return convertStatistics((IgniteSqlStatisticsCommand)cmd, pctx);
else if (cmd instanceof SqlCreateView)
return convertCreateView((SqlCreateView)cmd, pctx);
else if (cmd instanceof SqlDropView)
return convertDropView((SqlDropView)cmd, pctx);

throw new IgniteSQLException("Unsupported native operation [" +
"cmdName=" + (cmd == null ? null : cmd.getClass().getSimpleName()) + "; " +
Expand Down Expand Up @@ -198,6 +209,27 @@ private static SqlDropUserCommand convertDropUser(IgniteSqlDropUser sqlCmd, Plan
return new SqlDropUserCommand(sqlCmd.user().getSimple());
}

/**
* Converts CREATE VIEW ... command.
*/
private static SqlCreateViewCommand convertCreateView(SqlCreateView sqlCmd, PlanningContext ctx) {
String schemaName = deriveSchemaName(sqlCmd.name, ctx);
String viewName = deriveObjectName(sqlCmd.name, ctx, "View name");

return new SqlCreateViewCommand(schemaName, viewName,
sqlCmd.query.toSqlString(CalciteSqlDialect.DEFAULT).toString(), sqlCmd.getReplace());
}

/**
* Converts DROP VIEW ... command.
*/
private static SqlDropViewCommand convertDropView(SqlDropView sqlCmd, PlanningContext ctx) {
String schemaName = deriveSchemaName(sqlCmd.name, ctx);
String viewName = deriveObjectName(sqlCmd.name, ctx, "View name");

return new SqlDropViewCommand(schemaName, viewName, sqlCmd.ifExists);
}

/**
* Converts KILL ... command.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import org.apache.calcite.schema.Function;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Table;
import org.apache.calcite.schema.impl.AbstractSchema;

Expand All @@ -41,6 +41,9 @@ public class IgniteSchema extends AbstractSchema {
/** */
private final Multimap<String, Function> funcMap = Multimaps.synchronizedMultimap(HashMultimap.create());

/** */
private final Map<String, String> viewMap = new ConcurrentHashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the tables and the functions we store interfaces like Function or IgniteTable. For views a string/sql/request instead. Could we similary keep something like TableMacro. If I'm right, function register would not be required in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TableMacro requires schamaPlus. We don't have a schemaPlus until register method, so we can't use table macro here.


/**
* Creates a Schema.
*
Expand Down Expand Up @@ -88,4 +91,33 @@ public void removeTable(String tblName) {
public void addFunction(String name, Function func) {
funcMap.put(name, func);
}

/**
* @param name View name.
* @param sql View sql.
*/
public void addView(String name, String sql) {
viewMap.put(name, sql);
}

/**
* @param name View name.
*/
public void removeView(String name) {
viewMap.remove(name);
}

/**
* Registers current {@code IgniteSchema} in parent {@code SchemaPlus}.
*
* @param parent Parent schema.
* @return Registered schema.
*/
public SchemaPlus register(SchemaPlus parent) {
SchemaPlus newSchema = parent.add(schemaName, this);

viewMap.forEach((name, sql) -> newSchema.add(name, new ViewTableMacroImpl(sql, newSchema)));

return newSchema;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.apache.ignite.internal.processors.cache.GridCacheContextInfo;
import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
import org.apache.ignite.internal.processors.query.QueryField;
import org.apache.ignite.internal.processors.query.QueryUtils;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.IgniteScalarFunction;
import org.apache.ignite.internal.processors.query.calcite.trait.TraitUtils;
import org.apache.ignite.internal.processors.query.calcite.type.IgniteTypeFactory;
Expand Down Expand Up @@ -367,6 +368,26 @@ private static Object affinityIdentity(CacheConfiguration<?, ?> ccfg) {
rebuild();
}

/** {@inheritDoc} */
@Override public void onViewCreated(String schemaName, String viewName, String viewSql) {
IgniteSchema schema = igniteSchemas.computeIfAbsent(schemaName, IgniteSchema::new);

schema.addView(viewName, viewSql);

rebuild();
}

/** {@inheritDoc} */
@Override public void onViewDropped(String schemaName, String viewName) {
IgniteSchema schema = igniteSchemas.get(schemaName);

if (schema != null) {
schema.removeView(viewName);

rebuild();
}
}

/** {@inheritDoc} */
@Override public SchemaPlus schema(@Nullable String schema) {
return schema != null ? calciteSchema.getSubSchema(schema) : calciteSchema;
Expand All @@ -385,10 +406,14 @@ private IgniteCacheTable table(String schemaName, String tableName) {
/** */
private void rebuild() {
SchemaPlus newCalciteSchema = Frameworks.createRootSchema(false);

newCalciteSchema.add("UUID", typeFactory -> ((IgniteTypeFactory)typeFactory).createCustomType(UUID.class));
newCalciteSchema.add("OTHER", typeFactory -> ((IgniteTypeFactory)typeFactory).createCustomType(Object.class));
newCalciteSchema.add("PUBLIC", new IgniteSchema("PUBLIC"));
igniteSchemas.forEach(newCalciteSchema::add);
newCalciteSchema.add(QueryUtils.DFLT_SCHEMA, new IgniteSchema(QueryUtils.DFLT_SCHEMA));
alex-plekhanov marked this conversation as resolved.
Show resolved Hide resolved

for (IgniteSchema schema : igniteSchemas.values())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like similar to the previous: igniteSchemas.values().forEach(sch->sch.register(newCalciteSchema));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a matter of taste. sch->sch.register(newCalciteSchema) creates one additional object on each call for lambda.

schema.register(newCalciteSchema);

calciteSchema = newCalciteSchema;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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.
*/

package org.apache.ignite.internal.processors.query.calcite.schema;

import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.calcite.rel.type.RelProtoDataType;
import org.apache.calcite.schema.impl.ViewTable;
import org.jetbrains.annotations.Nullable;

/**
* Ignite SQL view table implementation.
*/
public class ViewTableImpl extends ViewTable {
/** Fields origins. */
private final Map<String, List<String>> origins;

/** Ctor. */
public ViewTableImpl(
Type elementType,
RelProtoDataType rowType,
String viewSql,
List<String> schemaPath,
Map<String, List<String>> origins
) {
super(elementType, rowType, viewSql, schemaPath, null);

this.origins = Collections.unmodifiableMap(origins);
}

/** Get field origin. */
public @Nullable List<String> fieldOrigin(String fieldName) {
return origins.get(fieldName);
}
}
Loading