how to build sql from RelBuild without schema info? - apache-calcite

i want to generate sql use calcite. like this
org.apache.calcite.rel.rel2sql.RelToSqlConverterTest#testAntiJoin
final FrameworkConfig frameworkConfig = Frameworks.newConfigBuilder()
.parserConfig(SqlParser.Config.DEFAULT)
// .defaultSchema(schema)
.build();
final RelBuilder builder = RelBuilder.create(frameworkConfig);
final RelBuilder builder = relBuilder();
final RelNode root = builder
.scan("DEPT")
.scan("EMP")
.join(
JoinRelType.ANTI, builder.equals(
builder.field(2, 1, "DEPTNO"),
builder.field(2, 0, "DEPTNO")))
.project(builder.field("DEPTNO"))
.build();
but if i don't set the schema, the exception table not found will be throw.
is there any way to generate sql without schema info.
the aim is generate sql. just generate sql.
reply for first answer. because comment character length limit.
My scenario is Business Intelligence. DataSource can be many, such as Hive, ClickHouse, and so on. And there are many tables. I also need to dynamically delete or add datasource. So I don't think it's appropriate for Calcite to be aware of all the data sources. I have two more questions:
How to create 'free-standing' table objects as you said
Check whether SqlNode can be used to do this. for example:
SqlIdentifier from = new SqlIdentifier("testTable", SqlParserPos.QUOTED_ZERO);
SqlNode[] nodes = new SqlNode[2];
nodes[0] = new SqlIdentifier("a", SqlParserPos.QUOTED_ZERO);
nodes[1] = SqlLiteral.createExactNumeric("1", SqlParserPos.QUOTED_ZERO);
SqlNode where = new SqlBasicCall(SqlStdOperatorTable.EQUALS, nodes, SqlParserPos.QUOTED_ZERO);
SqlIdentifier selectNode = new SqlIdentifier("a", SqlParserPos.QUOTED_ZERO);
SqlSelect select = new SqlSelect(SqlParserPos.QUOTED_ZERO, SqlNodeList.EMPTY,
new SqlNodeList(Arrays.asList(selectNode), SqlParserPos.QUOTED_ZERO),
from,
where,
null,
null,
null,
null,
null,
null,
null);
SqlString sqlString = select.toSqlString(CalciteSqlDialect.DEFAULT);
System.out.println(sqlString.getSql());

Only one method in RelBuilder uses a RelOptSchema: scan(String...) (and its variant Scan(Iterable<String>)). Which makes sense when you consider that the purpose of RelOptSchema is as a directory service, converting a table name (or table path, consisting of a table name qualified with catalog and/or schema names) into a RelOptTable object.
If you have 'free-standing' table objects that are not accessed via a namespace then you can create TableScan relational expressions directly and then call RelBuilder.push(RelNode) to add them to the stack. Since you never call RelBuilder.scan you can create RelBuilder with a null RelOptSchema.
But in your case, it looks as if you don't have free-standing table objects. That's a problem for Calcite, because it needs to know that your "EMP" table has a field called "DEPTNO" and it has type INTEGER.
So I suggest that you create a 'virtual' schema that contains type information but is not necessarily backed by real tables. The MockCatalogReader class, used in several of Calcite's tests, is a good example to follow.

Related

Can I add a string type parameter to a SQL statement without quotes?

I have a C++Builder SQL Statement with a parameter like
UnicodeString SQLStatement = "INSERT INTO TABLENAME (DATETIME) VALUES (:dateTime)"
Can I add the parameter without quotes?
Usually I'd use
TADOQuery *query = new TADOQuery(NULL);
query->Parameters->CreateParameter("dateTime", ftString, pdInput, 255, DateTimeToStr(Now()));
which will eventually produce the SQL String
INSERT INTO TABLENAME (DATETIME) VALUES ('2022-01-14 14:33:00.000')
but because this is a legacy project (of course, it always is) and I have to maintain different database technologies, I need to be able to inject database specific date time conversion methods, so that the endresult would look like
INSERT INTO TABLENAME (DATETIME) VALUES (to_date('2022-01-14 14:33:00.000', 'dd.mm.yyyy hh24:mi:ss')))
If I try injecting this via my 'usual' method (because I don't think I can inject a second parameter into this one) it'd look like:
TADOQuery *query = new TADOQuery(NULL);
query->Parameters->CreateParameter("dateTime", ftInteger, pdInput, 255, "to_date('" + DateTimeToStr(Now()) + "', 'dd.mm.yyyy hh24:mi:ss')");
but of course the result would look like:
INSERT INTO TABLENAME (DATETIME) VALUES ('to_date('2022-01-14 14:33:00.000', 'dd.mm.yyyy hh24:mi:ss')'))
and therefore be invalid
Or is there another way to do this more cleanly and elegantly? Although I'd settle with 'working'.
I can work around this by preparing two SQL Statements and switch the statement when another database technology is but I just wanted to check if there is another way.
Why are you defining the parameter's DataType as ftInteger when your input value is clearly NOT an integer? You should be defining the DataType as ftDateTime instead, and then assigning Now() as-is to the parameter's Value. Let the database engine decide how it wants to format the date/time value in the final SQL per its own rules.
query->Parameters->CreateParameter("dateTime", ftDateTime, pdInput, 0, Now());

Insert,update ,delete,drop sample code by using relation algebra

I am new Apache Calcite and am able to fetch the data from DB by using relational algebra ,but not able to do insert,update, delete,drop operation. If can share sample code will be more helpful.
As far as I am aware, RelBuilder can not build a RelNode for INSERT, UPDATE, DELETE, DROP operations.
For DML (INSERT, UPDATE, DELETE, MERGE), the equivalent relational algebra uses TableModify, so you can call LogicalTableModify.create to build one TableModify node and use RelBuilder to build a RelNode`` as its input as follows:
RelNode node = builder.scan("envliven").project("Name");
TableModify modifyNode = LogicalTableModify.create((table,
schema, node,
UPDATE, updateColumnList,
sourceExpressionList, flattened);
For DDL (DROP, CREATE, ALTER), there is no corresponding relational algebraļ¼Œbut you can use the SqlNode to execute directly like CalcitePrepareImpl.executeDdl.
For example:
update nation set n_nationkey = 1 where n_nationkey = 2;
RelNode as follows:
LogicalTableModify(table=[[test, nation]], operation=[UPDATE], updateColumnList=[[n_nationkey]], sourceExpressionList=[[1]], flattened=[false])
LogicalProject(n_nationkey=[$0], n_name=[$1], n_regionkey=[$2], n_comment=[$3], EXPR$0=[1])
LogicalFilter(condition=[=($0,2)])
LogicalTableScan(table=[[test, nation]])
For UPDATE, the updateColumnList has the column you updated, and the sourceExpressionList has the new values.
For INSERT:
insert into nation(n_nationkey, n_name) values(1, 'test');
RelNode as follows:
LogicalTableModify(table=[[test, nation]], operation=[INSERT], flattened=[false])
LogicalProject(n_nationkey=[$0], n_name=[$1], n_regionkey=[null], n_comment=[null])
LogicalValues(tuples=[[{ 1, _UTF-16'test ' }]])

CRM 2015 Microsoft.Xrm.Sdk: Unexpected results in second CreateQuery call

Microsoft.Xrm.Sdk, Version=7.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
var ctx = new ServiceContext(...);
var result1 = (from f in ctx.CreateQuery<aEntity>()
where f.field1 == "x"
select new { f.Id, f.field2 }).ToList();
var result2 = (from f in ctx.CreateQuery<aEntity>()
where f.field1 == "x"
select f.field1).First();
result2 returns null! After adding f.field1 to the select clause in the first query result2 returns "x". It looks like a internal columnset is created and used in the context of the second call. Looking at the SQL Server trace of both calls we see the expected select-from queries based on the C# code. The returned second result is not expected. Can someone explain this behaviour?
As for me it looks like a caching functionality and it's on the side of CRM because as you mentioned SQL queries were correct. I had the same issue in my applications when tried to make two consecutive queries for the same entity record but selected two different fields, the second request always returned NULL. Here are workarounds that I use when work with the ServiceContext:
Simple one: always retrieve an entity with all fields (without select statement) (even if I want it or not)
or create a service context with disabled caching
Right now I try to use the ServiceContext as less as possible replacing it with QueryBase expressions (even if I love to use LINQ).
Keep in mind LINQ CRM driver implementation is a subset of SQL only.
Could you try something like this?
var result1 = (from f in ctx.CreateQuery<aEntity>()
where f.field1 == "x"
select new CustomClass {
Id = f.aEntityId,
Field2 = f.field2
}).ToList();
You can have complex queries if you want, but you need to know what can be done and what can't be done.
Id property is not always returned by the driver, but the entity's primary key is, which is normally the entity logical name + "Id".

Transform a Sales Order into a Invoice with SuiteTalk

In NetSuite's SuiteTalk how do I transform a record from a Sales Order to an Invoice? It looks like there is a function in SuiteScript, but I can't find anything similar in SuiteTalk.
SuiteScript:
nlapiTransformRecord(type, id, transformType, transformValues)
Initializes a new record using data from an existing record of a
different type and returns an nlobjRecord. This function can be useful
for automated order processing such as creating item fulfillment
transactions and invoices off of orders.
SuiteTalk has an analogous initialize method. With the Java library you'd use it like:
ReadResponse initCS = nsClient.getPort().initialize(new InitializeRecord(InitializeType.cashSale, new InitializeRef(null, InitializeRefType.salesOrder, soId, null), null));
CashSale cs = (CashSale)initCS.getRecord();

Qt/SQL - Get column type and name from table without record

Using Qt, I have to connect to a database and list column's types and names from a table. I have two constraints:
1 The database type must not be a problem (This has to work on PostgreSQL, SQL Server, MySQL, ...)
2 When I looked on the internet, I found solutions that work but only if there are one or more reocrd into the table. And I have to get column's type and name with or without record into this database.
I searched a lot on the internet but I didn't find any solutions.
I am looking for an answer in Qt/C++ or using a query that can do that.
Thanks for help !
QSqlDriver::record() takes a table name and returns a QSqlRecord, from which you can fetch the fields using QSqlRecord::field().
So, given a QSqlDatabase db,
fetch the driver with db.driver(),
fetch the list of tables with db.tables(),
fetch the a QSqlRecord for each table from driver->record(tableName), and
fetch the number of fields with record.count() and the name and type with record.field(x)
According to the previous answers, I make the implementation as below.It can work well, hope it can help you.
{
QSqlDatabase db = QSqlDatabase::addDatabase("QSLITE", "demo_conn"); //create a db connection
QString strDBPath = "db_path";
db.setDatabaseName(strDBPath); //set the db file
QSqlRecord record = db.record("table_name"); //get the record of the certain table
int n = record.count();
for(int i = 0; i < n; i++)
{
QString strField = record.fieldName(i);
}
}
QSqlDatabase::removeDatabase("demo_conn"); //remove the db connection
Getting column names and types is a database-specific operation. But you can have a single C++ function that will use the correct sql query according to the QSqlDriver you currently use:
QStringlist getColumnNames()
{
QString sql;
if (db.driverName.contains("QOCI", Qt::CaseInsensitive))
{
sql = ...
}
else if (db.driverName.contains("QPSQL", Qt::CaseInsensitive))
{
sql = ...
}
else
{
qCritical() << "unsupported db";
return QStringlist();
}
QSqlQuery res = db.exec(sql);
...
// getting names from db-specific sql query results
}
I don't know of any existing mechanism in Qt which allows that (though it might exist - maybe by using QSqlTableModel). If noone else knows of such a thing, I would just do the following:
Create data classes to store the information you require, e.g. a class TableInfo which stores a list of ColumnInfo objects which have a name and a type.
Create an interface e.g. ITableInfoReader which has a pure virtual TableInfo* retrieveTableInfo( const QString& tableName ) method.
Create one subclass of ITableInfoReader for every database you want to support. This allows doing queries which are only supported on one or a subset of all databases.
Create a TableInfoReaderFactory class which allows creation of the appropriate ITableInfoReader subclass dependent on the used database
This allows you to have your main code independent from the database, by using only the ITableInfoReader interface.
Example:
Input:
database: The QSqlDatabase which is used for executing queries
tableName: The name of the table to retrieve information about
ITableInfoReader* tableInfoReader =
_tableInfoReaderFactory.createTableReader( database );
QList< ColumnInfo* > columnInfos = tableInfoReader->retrieveTableInfo( tableName );
foreach( ColumnInfo* columnInfo, columnInfos )
{
qDebug() << columnInfo.name() << columnInfo.type();
}
I found the solution. You just have to call the record function from QSqlDatabase. You have an empty record but you can still read column types and names.