using an expression as table name in sqlite - sqlite

I am trying to check if a table exists prior to send a SELECT query on that table.
The table name is composed with a trailing 2 letters language code and when I get the full table name with the user's language in it, I don't know if the user language is actually supported by my database and if the table for that language really exists.
SELECT name FROM sqlite_master WHERE name = 'mytable_zz' OR name = 'mytable_en' ORDER BY ( name = 'mytable_zz' ) DESC LIMIT 1;
and then
SELECT * FROM table_name_returned_by_first_query;
I could have a first query to check the existence of the table like the one above, which returns mytable_zz if that table exists or mytable_en if it doesn't, and then make a second query using the result of the first as table name.
But I would rather have it all in one single query that would return the expected results from either the user's language table or the english one in case his language is not supported, without throwing a "table mytable_zz doesn't exist" error.
Anyone knows how I could handle this ?
Is there a way to use the result of the first query as a table name in the 2nd ?
edit : I don't have the hand of the database itself which is generated automatically, I don't want to get involved in a complex process of manually updating any new database that I get. Plus this query is called multiple times and having to retrieve the result of a first query before launching a second one is too long. I use plain text queries that I send through a SQLite wrapper. I guess the simplest would rather be to check if the user's language is supported once for all in my program and store a string with either the language code of the user or "en" if not supported, and use that string to compose my table name(s). I am going to pick that solution unless someone has a better idea
Here is a simple MRE :
CREATE TABLE IF NOT EXISTS `lng_en` ( key TEXT, value TEXT );
CREATE TABLE IF NOT EXISTS `lng_fr` ( key TEXT, value TEXT );
INSERT INTO `lng_en` ( key , value ) VALUES ( 'question1', 'What is your name ?');
INSERT INTO `lng_fr` ( key , value ) VALUES ( 'question1', 'Quel est votre nom ?');
SELECT `value` FROM lng_%s WHERE `key` = 'question1';
where %s is to be replaced by the 2 letters language code. This example will work if the provided code is 'en' or 'fr' but will throw an error if the code is 'zh', in this case I would like to have the same result returned as with 'en' ....

Not in SQL, without executing it dynamically.. But if this is your front end that is running this SQL then it doesn't matter so much. Because your table name came out of the DB there isn't really any opportunity for SQL injection hacking with it:
var tabName = db.ExecuteScalar("SELECT name FROM sqlite_master WHERE name = 'mytable_zz' OR name = 'mytable_en' ORDER BY ( name = 'mytable_zz' ) DESC LIMIT 1;")
var results = db.ExecuteQuery("SELECT * FROM " + tabName);
Yunnosch's comment is quite pertinent; you're essentially storing in a table name information that really should be in a column.. You could consider making a single table and then a bunch of views like mytable_zz the definition of which is SELECT * FROM mytable WHERE lang = 'zz' etc, and make instead-of triggers if you want to cater for a legacy app that you cannot change; the legacy app would select from / insert into the views thinking they are tables, but in reality your data is single table and easier to manage

Related

SQLite3 generated column does not show up in QSqlRelationalTableModel

I have a QTableView connected to a QSqlRelationalTableModel. Everything works as expected, however, it seems to be impossible to have a sqlite3 generated column in my model:
bhd [INTEGER] GENERATED ALWAYS AS (round(d_mess * 130 / bhd_hoehe)) STORED
The above statement is the default way to add a generated column in sqlite3. But it does not appear in my model. When I change the column to a standard integer column, it's there.
It does not matter if the generated column is of type STORED or VIRTUAL or if I omit the GENERATED ALWAYS keyword.
Is this by design? Or a sqlite3 driver problem?
Views seem not to be affected by this issue. I'm aware it's more a workaround than a solution, but you could create a dummy view just with your foreign key and the generated field.
For example, I have a simple table User like this:
CREATE TABLE User (
ID INTEGER PRIMARY KEY AUTOINCREMENT
UNIQUE,
name TEXT,
surname TEXT,
title TEXT,
full_name GENERATED ALWAYS AS (title || ' ' || surname || ' ' || name) STORED
);
As is, no QSqlRelation pointing to User.full_name will work, as you pointed out. However, I've created a simple view like
CREATE VIEW UserFullNameView AS
SELECT ID,
full_name
FROM User;
Now the following binding works fine
model: QSqlRelationalTableModel
model.setRelation(foreign_field, QSqlRelation('UserFullNameView', 'ID', 'full_name')

Do you need parameterized SQL searches if you check the inputs?

I'm writing an R Shiny/SQLite app. In the app, I have a function that returns a column from one of the tables in my SQLite database, with the function taking the table name as an input.
Before sending the query to SQLite, the function checks that the table name equals one of the table names that the user is allowed to access. However, I am not using a parameterized query, because the term I'm changing is not a variable used for comparison but the name of the table to extract information from. (There might be a way to make this work anyway with a parameterized search, I suppose.)
My question is whether this is safe from an SQL injection? Can the query be altered on its way from the server to the database, or only from an alteration in the ui input to the server?
(Bear with me, I am new to SQLite.)
Assuming your query is being concatenated as follows:
tbl <- "yourTable"
sql <- paste0("select * from ", tbl, " where some_col = 1")
Then there should be no chance of SQL injection, assuming you check the incoming table name and verify that it matches a table name in your whitelist. Note that this step is critical here to keeping things safe. Let's say that you didn't sterilize the incoming table name. Then, consider this:
tbl <- "yourTable; delete from yourTable"
This would result in the following query being submitted for execution:
select * from yourTable; delete from yourTable where some_col = 1;
Assuming your SQLite driver allows multiple SQL statements to execute, the above hack/trick might end up deleting data from a large portion of one of your tables.
So, your approach should be safe provided that you check the table name. Note that strictly speaking the table name itself is not a parameter in a parameterized query. Rather, only the literal values in the query are parameters.
SQL query parameters cannot be used in place of a table name anyway, so comparing the table name to a list of known authorized tables is your only option.
Yes, it is safe. If you're in control of the set of values that can be interpolated into the SQL query, then you can prevent unauthorized SQL injection.
Note that some other elements of SQL queries cannot be parameters:
Any identifier, e.g. a table name, column name, or schema name.
Expressions
Lists of values in an IN ( ... ) predicate. Use one parameter per value in the list.
SQL keywords.
A query parameter can be used only in place of a single scalar value. That is, where you would use a quoted string literal, quoted date literal, or numeric literal.
The problem of SQL injection is only the user input. Nothing happens to the query on its way from the server to the database (well a malware could in theory alter it, but then even a parametrized query wouldn't help).
I.e., if you create a SQL string like this (C#):
sql = "SELECT * FROM " + tableName;
Then a user might enter a tableName like
MyTable; DROP TABLE MyTable
Guess what happens.
So, if you check the table name, you are on the safe side.

Need to get data from a table using database link where database name is dynamic

I am working on a system where I need to create a view.I have two databases
1.CDR_DB
2.EMS_DB
I want to create the view on the EMS_DB using table from CDR_DB. This I am trying to do via dblink.
The dblink is created at the runtime, i.e. DB Name is decided at the time user installs the database, based on the dbname dblink is decided.
My issue is I am trying to create a query like below to create a view from a table which name is decided at run time. Please see below query :
select count(*)
from (SELECT CONCAT('cdr_log#', alias) db_name
FROM ems_dbs a,
cdr_manager b
WHERE a.db_type = 'CDR'
and a.ems_db_id = b.cdr_db_id
and b.op_state = 4 ) db_name;
In this query cdr_log#"db_name" is the runtime table name(db_name get's created at runtime).
When I'm trying to run above query, I'm not getting the desired result. The result of the above query is '1'.
When running only the sub-query from the above query :
SELECT CONCAT('cdr_log#', alias) db_name
FROM ems_dbs a,
cdr_manager b
WHERE a.db_type = 'CDR'
and a.ems_db_id = b.cdr_db_id
and b.op_state = 4;
i'm getting the desired result, i.e. cdr_log#cdrdb01
but when i'm trying to run the full query, getting result as '1'.
Also, when i'm trying to run as
select count(*) from cdr_log#cdrdb01;
I'm getting the result as '24' which is correct.
Expected Result is that I should get the same output similar to the query :
select count(*) from cdr_log#cdrdb01;
---24
But the desired result is coming as '1' using the full query mentioned initially.
Please let me know a way to solve the above problem. I found a way to do it via a procedure, but i'm not sure how can I invoke this procedure.
Can this be done as part of sub query as I have used above?
You're not going to be able to create a view that will dynamically reference an object over a database link unless you do something like create a pipelined table function that builds the SQL dynamically.
If the database link is created and named dynamically at installation time, it would probably make the most sense to create any objects that depend on the database link (such as the view) at installation time too. Dynamic SQL tends to be much harder to write, maintain, and debug than static SQL so it would make sense to minimize the amount of dynamic SQL you need. If you can dynamically create the view at installation time, that's likely the easiest option. Even better than directly referencing the remote object in the view, particularly if there are multiple objects that need to reference the remote object, would probably be to have the view reference a synonym and create the synonym at install time. Something like
create synonym cdr_log_remote
for cdr#<<dblink name>>
create or replace view view_name
as
select *
from cdr_log_remote;
If you don't want to create the synonym/ view at installation time, you'd need to use dynamic SQL to reference the remote object. You can't use dynamic SQL as the SELECT statement in a view so you'd need to do something like have a view reference a pipelined table function that invokes dynamic SQL to call the remote object. That's a fair amount of work but it would look something like this
-- Define an object that has the same set of columns as the remote object
create type typ_cdr_log as object (
col1 number,
col2 varchar2(100)
);
create type tbl_cdr_log as table of typ_cdr_log;
create or replace function getAllCDRLog
return tbl_cdr_log
pipelined
is
l_rows typ_cdr_log;
l_sql varchar(1000);
l_dblink_name varchar(100);
begin
SELECT alias db_name
INTO l_dblink_name
FROM ems_dbs a,
cdr_manager b
WHERE a.db_type = 'CDR'
and a.ems_db_id = b.cdr_db_id
and b.op_state = 4;
l_sql := 'SELECT col1, col2 FROM cdr_log#' || l_dblink_name;
execute immediate l_sql
bulk collect into l_rows;
for i in 1 .. l_rows.count
loop
pipe row( l_rows(i) );
end loop;
return;
end;
create or replace view view_name
as
select *
from table( getAllCDRLog );
Note that this will not be a particularly efficient way to structure things if there are a large number of rows in the remote table since it reads all the rows into memory before starting to return them back to the caller. There are plenty of ways to make the pipelined table function more efficient but they'll tend to make the code more complicated.

Is it okay to use .Query<table_name> when updating SQLite using Xamarin?

I have taken over some code and I see that database updates are performed like this:
dbcon = DependencyService.Get<ISQLite>().GetConnection();
public void UpdateAnswered(string id)
{
lock(locker)
{
dbcon.Query<Phrase>("UPDATE Phrase SET Answered = Answered + 1 " +
"WHERE Id = ?", id);
}
}
I am new to using SQLite with Xamarin but it looks strange to me that this update is handled with a dbcon.Query and that the table name is passed as . Can someone confirm is this the optimal way to handle a table update? Also why is it coded as a query with the table name being passed?
Update<T>
This method allows you to pass in an instance of an object that this stored in the database which has a primary key. SQLite then recognizes the primary key and updates the rest of the object's values.
You would just call connection.Update( phrase ); where the phrase is an instance of the Phrase class with properties you want to set. Be aware that all columns except ID will be updated.
Query<T>
Performs a query and returns the results. The type parameter specifies the type of the items returned. This is most appropriate for SELECT queries.
Execute
This returns the number of affected rows by the query as an int. This is probably the best choice for your UPDATE query after the Update<T> method.
ExecuteScalar<T>
Use for queries that return scalar types - like COUNT, etc., where T is the type of the value.
In summary, Update is the most natural way to update a row in the database (with an instance you have), but Query<T> and Execute<T> are very useful if you just want to UPDATE one column like in your example.

SQLite Insert or Ignore/Replace with lastInsertID in one statement

I feel like this should be easy...
I have a table like this:
CREATE TABLE table_name (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
UNIQUE (name COLLATE NOCASE)
)
No two names are the same, case insensitive. Right now I have users adding names and it does this:
INSERT INTO table_name (name) VALUES ("my name");
And I need to get the id of the row, which is easy with PHP PDO's lastInsertID(). But I also want, if the user is adding a name that's already in the database, for nothing to be added to the database, but still get that id without having to do another database call. I was hoping for something like
INSERT OR REPLACE INTO table_name (name) VALUES ("my name");
And have it just overwrite the same data into the cell and return the lastInsertID (even though it wasn't inserted?). But that doesn't work. What are my other options? Will I have to do a separate database query to see if the name field already exists?
With the OR REPLACE clause, the statement always deletes any old row.
Just use two statements. (There is no technical reason for doing this in a single statement.)

Resources