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

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.

Related

how to build sql from RelBuild without schema info?

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.

SQLite "On delete CASCADE" not cascading in Qt

I have a database in Qt.
it has four tables: maingroup, subgroup, parts, and position.this is my database:
CREATE TABLE `maingroup` (
`groupName`TEXT NOT NULL UNIQUE,
PRIMARY KEY(`groupName`)
);
CREATE TABLE `subgroup` (
`sub` TEXT NOT NULL UNIQUE,
`main` TEXT NOT NULL,
PRIMARY KEY(`sub`),
FOREIGN KEY(`main`) REFERENCES `maingroup`(`groupName`) ON DELETE CASCADE
);
CREATE TABLE `parts` (
`ID` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
`Part_Number` TEXT,
`Type` TEXT NOT NULL,
`Value` TEXT,
`Voltage` TEXT,
`Quantity` TEXT,
`Position` TEXT,
`Picture` TEXT,
FOREIGN KEY(`Position`) REFERENCES `Position`(`Poistion`) ON DELETE CASCADE,
FOREIGN KEY(`Type`) REFERENCES `subgroup`(`sub`) ON DELETE CASCADE
);
Type in table parts is foreign key refers to column sub from table subgroup.
main in table subgroup is foreign key refers to column groupname in table maingroup.
my problem is when I try (delete from maingroup WHERE groupName= 'dd';) in DB Browser it deletes both parent and children.
But in QT this command(myQuery.exec("delete from maingroup WHERE groupName= 'dd'");) just deletes the parent field in maingroup table and not the child in subgroup and part table and the main column in subgroup table refers to a field in maingroup table that does not exist.
what is wrong here?what should i do?
You need to turn on the foreign-key pragma by executing another statement before your DELETE statement.
QSqlQuery q;
q.exec("PRAGMA foreign_keys = ON");
q.exec("DELETE FROM ...");
This was able to cascade deletes, and should also be sufficient to solve other foreign-key related issues.
Credits to this forum.qt.io post.
In addition to #TrebledJ correct and very helpful answer it's worth to mention two additional characteristics about the foreign-key pragma (in connection with Qt):
1. The pragma can set via QSqlDatabase, too.
So the following code has the same effect as #TrebledJ's example:
auto database = QSqlDatabase::database();
database.exec("PRAGMA foreign_keys = ON");
QSqlQuery query(database); // query "inherits" the pragma from database
query.exec("DELETE FROM ...");
2. This behavior even applies if opening and using the database happens at different places in the program.
Still the same effect:
Somewhere in initialization code:
// this calls database.open() implicit because database was not open before.
auto database = QSqlDatabase::database();
QSqlDatabase::database();
database.exec("PRAGMA foreign_keys = ON");
// make sure database.close() is NOT called!
Somewhere else in code
// you'll get the instance of your initialization code because database is already open (so QSqlDatabase::database() implements a singleton pattern).
// so pragma foreign_keys is still set to "ON"
auto database = QSqlDatabase::database();
QSqlQuery query(database);
query.exec("DELETE FROM ...");
This might be important to know if you want to understand why the foreign_keys pragma seems to only sometimes apply in your project (and sometimes not).
What I did
So I came to the conclusion to make sure to have ONE distinct place in my code where to explicit open the database (and configure the connection):
QString dbConnectionName = "My project database";
auto database = QSqlDatabase::database(dbConnectionName, true);
// configure the pragmas here
At all other places I avoid to (accidentally) open the database:
auto database = QSqlDatabase::database(dbConnectionName, false);
// e.g. use the database via queries ...

Qt/C++ How to get foreign keys for a table in different databases

I am creating an app using Qt/C++, which should be able to connect to different types of databases and get it's structure and info from there. And regarding DB structure I've found methods how to get list of tables and their columns without knowledge of DB type, using something like that:
QSqlDatabase db_ = QSqlDatabase::addDatabase(dbType);
db_.setDatabaseName(dbName);
db_.setUserName(user);
db_.setPassword(password);
db_.setHostName(hostName);
db_.setPort(port);
QStringList tables = db_.tables();
auto driver = db_.driver();
QSqlRecord record = driver->record(tableName);
QStringList fields;
for (int i = 0; i < record.count(); i++) {
fields.append(record.field(i).name());
}
But I can't find any method to get the foreign keys in a table without using direct queries to DB. I would be very thankful for your help.

Get duplicated record by using QSqlTableModel

I have a table that contains duplicated records.
I used following code to put the data into a QTableView:
QSqlTableModel *dataModel = new QSqlTableModel();
dataModel->setTable("table_name");
dataModel->select();
now I wanna to query duplicated records. I use group by and having for do that in sql but I haven`t any idea to how do this in qt.
Finally I found the solution, I used "setFilter" method as bellow:
dataModel = new QSqlTableModel();
dataModel->setTable("CUSTOMER");
QString filter_txt = "id in (select id FROM CUSTOMER GROUP BY id HAVING count(*) >1)" ;
dataModel->setFilter(filter);
dataModel->select();

Creating a new table in database with description from existing table as an input

I have written a QT GUI application that connects to an Oracle Database and performs query and shows the output in a QTableView.
QString host_name=ui->lineHostName->text();
QString db_name=ui->lineDatabaseName->text();
QString user_name=ui->lineUserName->text();
QString pass_word=ui->linePassword->text();
QString port_no=ui->linePortNumber->text();
QSqlDatabase db = QSqlDatabase::addDatabase("QOCI");
db.setHostName(host_name);
db.setDatabaseName(db_name);
db.setUserName(user_name);
db.setPassword(pass_word);
db.setPort(port_no.toInt());
QString MyQuery = ui->lineQuery->text();
db.open();
QSqlQuery query(MyQuery,db);
if(query.exec())
{
this->model=new QSqlQueryModel();
model->setQuery(MyQuery);
ui->tableViewOra->setModel(model);
}
After running the program, I tried to use this (as a substitute of the DESC )---
SELECT
column_name "Name",
nullable "Null?",
concat(concat(concat(data_type,'('),data_length),')') "Type"
FROM user_tab_columns
WHERE table_name='my_table_number_one';
And the column names, null parameter and data type was shown on the QTableView
Now my question is, can I use this information in the QTableView to create another table with same Column names and data type ??? (Basically creating a copy of my queried table table).
EDIT
After suggestions i tried modifying with---
QString query_to_replicate;
query_to_replicate=QString("CREATE OR REPLACE TABLE %1 AS %2").arg("AJ_REPLACEMENT_TESTING").arg(ui->lineEdit->text());
QSqlQuery query_second(query_to_replicate,db);
if(query_second.exec())
{
ui->r_pop_Button->setStyleSheet("QPushButton {background-color: rgb(0, 255, 0);}");
this->model_relocate=new QSqlQueryModel();
model_relocate->setQuery(query_second);
ui->tableView2->setModel(model_relocate);
while (model_relocate->canFetchMore())
model_relocate->fetchMore();
qDebug()<<QDateTime::currentDateTime()<<"Query SUCCESS ";
db.close();
}
now it worked twice, without throwing errors and created replicated copies in the oracle database (I used different names for replicated table before building).
But after running successfully twice, it seized replicating. Situation is completely clueless. I am not getting any errors during build / compile time.
You can do this by using CREATE TABLE AS SELECT:
QString sql = "CREATE TABLE %1 AS %2"
.arg(yourNewTableName)
.arg(ui->lineQuery->text());
// and execute this sql code on your QSqlDatabase as you do it above
It will create new table, with name from "yourNewTableName" variable and copy data from select query to new table.
Code update:
QString query_to_replicate;
query_to_replicate=QString("CREATE OR REPLACE TABLE %1 AS %2").arg("AJ_REPLACEMENT_TESTING").arg(ui->lineEdit->text());
QSqlQuery query_second(query_to_replicate,db); // query will be executed there! weird, but...
if (query_second.lastError().isValid())
{
qDebug() << query_second.lastError().text(); // error happens
}
else
{
qDebug() << "Table created successfully";
}
Also, you must #include <QSqlError> in the top of file, to use QtSql errors.