My go app shall support multiple databases. Meaning, running the same binary with different databases, the database the app is working with will be determined by configuration.
The problem is, each database has it's own prepared statements syntax.
Example:
db.Prepare("select p, f, t from mytable where p = $1")
Will work for postgres but will not work for mysql.
db.Prepare("select p, f, t from mytable where p = ?")
Will work for mysql but will not work for postgres.
Off curse I can solve it by editing the string on runtime or maintaining multiple queries.
Is there a better way?
I do not want to have some huge abstraction with an external library that will take control on all my db access, but if there is some light weight library that just magically fix the syntax, I am good with that.
EDIT:
Summarising what I have said before, the part that bothers me is that for mysql you will have to use "?" while for postgres you will have to use $1, $2...
Cheers
I found db.Rebind() to help with this. So:
name := "my name"
var p = property{}
// language=SQL
s := "SELECT * FROM property WHERE name=?"
err := db.Get(&p, db.Rebind(s), name)
The language comment at the top is so that IntelliJ can still syntax-check the SQL statement for me in the UI.
I also had to write separate CREATE statements for each database (my application is simultaneously supporting mysql, postgres, and sqlite).
I also found the UPDATE statement syntax between mysql and sqlite to be the same, but postgres required special handling. Since my UPDATE statements are very consistent, I was able to write a function to just kludge-translate from the mysql dialect to the postgres dialect. This is definitely not a generic solution but worked well enough for my unit and integration tests to pass. YMMV.
// RebindMore takes a MySQL SQL string and convert it to Postgres if necessary.
// The db.Rebind() handles converting '?' to '$1', but does not handle SQL statement
// syntactic changes needed by Postgres.
//
// convert: "UPDATE table_name SET a = ?, b = ?, c = ? WHERE d = ?"
// to: "UPDATE table_name SET (a, b, c) = ROW (?, ?, ?) WHERE d = ?"
func RebindMore(db *sqlx.DB, s string) string {
if db.DriverName() != "postgres" {
return s
}
if !strings.HasPrefix(strings.ToLower(s), "update") {
return db.Rebind(s)
}
// Convert a MySQL update statement into a Postgres update statement.
var idx int
idx = strings.Index(strings.ToLower(s), "set")
if idx < 0 {
log.Fatal().Msg("no SET clause in RebindMore (" + s + ")")
}
prefix := s[:idx+3]
s2 := s[idx+3:]
idx = strings.Index(strings.ToLower(s2), "where")
if idx < 0 {
log.Fatal().Msg("no WHERE clause in RebindMore (" + s + ")")
}
suffix := s2[idx:]
s3 := s2[:idx]
s4 := strings.TrimSpace(s3)
arr := strings.Split(s4, ",")
var names = ""
var values = ""
for i := 0; i < len(arr); i++ {
nameEqValue := arr[i]
s5 := strings.ReplaceAll(nameEqValue, " ", "")
nvArr := strings.Split(s5, "=")
if names != "" {
names += ","
}
names += nvArr[0]
if values != "" {
values += ","
}
values += nvArr[1]
}
s6 := prefix + " (" + names + ") = ROW (" + values + ") " + suffix
return db.Rebind(s6)
}
Call it this way:
// language=SQL
s := RebindMore(db, "UPDATE table_name SET a = ?, b = ? WHERE c = ?")
db.MustExec(s, value1, value2)
At some point I will need to add migration, and expect to just add separate code per DB to handle the differences (like for CREATE).
One final thing worth pointing out is that MySQL and Postgres handle capitalization very differently. I ended up just converting every table and column name to lower_case to avoid unnecessary complexity.
In this particular case use a place holder {{ph}} in the end of the SQL and use strings.Replace() to replace it with ? Or $1 according to the db driver.
Related
How can I build a dynamic query depending on the parameters that I get?
This example is stupid and the syntax is wrong but you will get the idea of what I want.
I guess that I need to add a slice of variables to the end of the query.
I know how to do it in PHP, but not in golang.
db := OpenDB()
defer db.Close()
var filter string
if name != "" {
filter = filter " AND name = ?"
}
if surname != "" {
filter = filter + " AND surname = ?"
}
if address != "" {
filter = filter + " AND address = ?"
}
err = db.Query("SELECT id FROM users WHERE login = ?" +
filter, login)
To answer your question on how to format the string, the simple answer is to use fmt.Sprintf to structure your string. However see further down for a quick note on using fmt.Sprintf for db queries:
Sprintf formats according to a format specifier and returns the resulting string.
Example:
query := fmt.Sprintf("SELECT id FROM users WHERE login='%s'", login)
err = db.Query(query)
// Equivalent to:
rows, err := db.Query("SELECT id FROM users WHERE login=?", login)
Using this for queries, you're safe from injections. That being said, you might be tempted to modify this and use db.Exec for creations/updates/deletions as well. As a general rule of thumb, if you use db.Exec with fmt.Sprintf and do not sanitize your inputs first, you open yourself up to sql injections.
GoPlay with simple example of why fmt.Sprintf with db.Exec is bad:
https://play.golang.org/p/-IWyymAg_Q
You should use db.Query or db.Prepare in an appropriate way to avoid these sorts of attack vectors. You might have to modify the code sample above to come up with a injection-safe snippet, but hopefully I gave you enough to get started.
I a C program I have a function that takes in parameter a domain name:
void db_domains_query(char *name);
With mysql_query() I test if the domain name is existing in a database. If it's not the case, I insert the new domain name:
...
char *query[400];
sprintf(query, "SELECT Id, DomainName FROM domains WHERE domainName LIKE '%s'", name);
if (mysql_query(con, query))
finish_with_error(con);
MYSQL_RES *result = mysql_store_result(con);
if (result == NULL)
finish_with_error(con);
MYSQL_ROW row;
if ((row = mysql_fetch_row(result)))
printf("Element exists : %s %s\n", row[0], row[1]);
else
printf("Element %s doesn't found\n", name);
// Then insert the new domain name ...
This portion of code works perfectly if name contains only "normal characters". However, for domain names that contain "special characters" the query seems incorrect even if those are in the database for instance :
name = undaben.de : Element exists : 100 undaben.de
name = ®here.com : Element ®here.com is not found.
name = §travel.us : Element §travel.us is not found.
Extract of the table :
+-----+--------------+
| id | domainname |
+-----+--------------+
| 100 | undaben.de |
| 162 | §travel.us |
| 197 | ®here.com |
+-----+--------------+
The collation of the field domainname is utf8_unicode_ci.
So how can I pass to mysql_query all domain names including the "special" ones ?
I recommend you to avoid the C API unless you have a compelling reason to use it. The C++ API es way more usable.
You are embedding your arguments within your query string. This has a number of problems, including security risks. If you insist in this approach, in order to prevent problems with parameters messing with your query, you need to ensure a few things:
Make sure that your data encoding matches the encoding of the MySQL Client connection (this may be different from your database encoding). If your connection is set up as UTF-8, then you need to make sure that special characters such as © are encoded also in UTF-8 when used as input to the sprintf function.
You also need to protect from other SQL escape characters (like '). For this you can use the mysql_real_escape_string function, as mentioned in Efficiently escaping quotes in C before passing to mysql_query.
However, you should very likely be using prepared statements which circumvent these issues. You still need to make sure that your input data encoding matches the encoding of your client connection, but everything else shall be easier to handle.
I paste an example of a parameterized query using the C API with prepared statements looks like (example from http://lgallardo.com/2011/06/23/sentencias-preparadas-de-mysql-en-c-ejemplo-completo/). Note the example is for integers, not strings, you need to adapt to your use case.
sql = "select count(*) from addresses where id = ?";
// Open Database
openDB(&conn);
// Allocate statement handler
stmt = mysql_stmt_init(conn);
if (stmt == NULL) {
print_error(conn, "Could not initialize statement handler");
return;
}
// Prepare the statement
if (mysql_stmt_prepare(stmt, sql, strlen(sql)) != 0) {
print_stmt_error(stmt, "Could not prepare statement");
return;
}
// Initialize the result column structures
memset (param, 0, sizeof (param)); /* zero the structures */
memset (result, 0, sizeof (result)); /* zero the structures */
// Init param structure
// Select
param[0].buffer_type = MYSQL_TYPE_LONG;
param[0].buffer = (void *) &myId;
param[0].is_unsigned = 0;
param[0].is_null = 0;
param[0].length = 0;
// Result
result[0].buffer_type = MYSQL_TYPE_LONG;
result[0].buffer = (void *) &myNumAddresses;
result[0].is_unsigned = 0;
result[0].is_null = &is_null[0];
result[0].length = 0;
// Bind param structure to statement
if (mysql_stmt_bind_param(stmt, param) != 0) {
print_stmt_error(stmt, "Could not bind parameters");
return;
}
// Bind result
if (mysql_stmt_bind_result(stmt, result) != 0) {
print_stmt_error(stmt, "Could not bind results");
return;
}
// Set bind parameters
myId = id;
// Execute!!
if (mysql_stmt_execute(stmt) != 0) {
print_stmt_error(stmt, "Could not execute statement");
return;
}
if (mysql_stmt_store_result(stmt) != 0) {
print_stmt_error(stmt, "Could not buffer result set");
return;
}
// Init data
(*numAddresses) = 0;
// Fetch
if(mysql_stmt_fetch (stmt) == 0){
(*numAddresses) = myNumAddresses;
}
// Deallocate result set
mysql_stmt_free_result(stmt); /* deallocate result set */
// Close the statement
mysql_stmt_close(stmt);
// Close Database
closeDB(conn);
Again, if you can use some other client library (like the C++ client) your code will be way shorter and readable.
My bad, as #jjmontes mentioned it seems that the sent string was encoded in 'latin1'.
Using the function mysql_set_character_set(conn, "utf8") before doing the query solved this problem.
Now, I will try to use prepared statements instead of query strings.
thanks again!
I want to use the "?" char for a search in a MySQL request in rails.
For example, classical way is:
Model.where("name = ?", '#{#search}')
My question is about long queries and multilines condition.
If I want to build a condition manually:
where = ""
where << " status = 1 "
where << " AND MATCH (name) AGAINST (? IN BOOLEAN MODE), #search " if #search
#records = Model.where(where)
Of course, it won't work.
So how to use the "?" (for security and simplicity) with a multilines conditions ?
A simple way is to do that:
where << " MATCH (name) AGAINST ('#{#search}' IN BOOLEAN MODE) "
But I will lose security (SQL injection) and can have problems with quotes if #search contains quotes.
Thanks,
You're getting a bit confused about the contents of where: you're putting variable names inside the string, which won't work: "#search" inside a string becomes literally the word "#search" and not a variable.
The best way to think of the arguments to where is as an array of objects, and you can build it like this. The first object is the query string (with ? symbols) and the other elements are the values for the ? symbols, which will be sanitized and translated by rails.
eg
User.where(["first_name = ? and last_name = ?", "John", "Smith"])
you can pass other things to where, like a hash of values, or a single string, but the array is the most flexible, especially in your case.
Bearing that in mind, you can do something like this to build a dynamically-created, complex query: i use this pattern a lot as it's very flexible and also very readable.
condition_strings = []
condition_values = []
condition_strings << "status = ?"
condition_values << 1 #or some dynamic data
condition_strings << "MATCH (name) AGAINST (? IN BOOLEAN MODE)"
condition_values << #search
conditions = [condition_strings.join(" AND ")] + condition_values
# => ["status = ? AND MATCH (name) AGAINST (? IN BOOLEAN MODE)", 1, "foo"]
#now we can use the array as an argument to `.where`:
#records = Model.where(conditions)
I am trying to prevent SQL injection in my Java program. I want to use PreparedStatements to do this, but I don't know the number of columns or their names in advance (the program allows administrators to add and remove columns from the tables). I'm new to this, so this may be a silly question, but I'm wondering if this approach is safe:
public static int executeInsert( String table, Vector<String> values)
{
Connection con;
try {
con = connect();
// Construct INSERT statement
int numCols = values.size();
String selectStatement = "INSERT INTO " + table + " VALUES (?";
for (int i=1; i<numCols; i++) {
selectStatement += ", ?";
}
selectStatement += ")";
PreparedStatement prepStmt = con.prepareStatement(selectStatement);
// Set the parameters for the statement
for (int j=0; j<numCols; j++) {
prepStmt.setString(j, values.get(j));
}
System.out.println( "SQL: " + prepStmt) ;
int result = prepStmt.executeUpdate();
con.close() ;
return( result) ;
} catch (SQLException e) {
System.err.println( "SQL EXCEPTION" ) ;
System.err.println( "Inserting values " + values + " into " + table);
e.printStackTrace();
}
return -1;
}
Basically I'm creating the String for the statement dynamically based on how many values are passed in (and therefore how many columns are in the table). I feel like it's safe because the PreparedStatement is not actually created until after this string is made. I may make similar functions that take in actual column names and incorporate them into the SQL statement, but these will be produced by my program and not based on user input.
Any time you have values like table being inserted into your query without escaping, you should test against a whitelist of known-good values. This prevents people from being creative and causing trouble. A simple dictionary or array of valid entries usually suffices.
Using a prepared statement is a good idea, but be sure the statement you're preparing doesn't allow for injections right from the start.
I have selected multiple columns from my table, but I don't know how to pass it to my view.
var result = (from f in db.firmware
where f.firmware_release_type_text != ""
|| f.firmware_release_type_text != null
|| f.firmware_release_number_int != 0
select new{
f.firmware_release_type_text,
f.firmware_release_number_int
}).Distinct();
The result is f__anonymous2. I want to some how use it in my view. all the forums have just answered how to choose multiple columns, but nobody mentions how to pass them. I think I'm missing something obvious.
I want to be able to use this fields, or even merge them as one string.
I have tried Cast and so many other options which did not work.
When I try to force casting it sting, I get :
Unable to cast the type 'Anonymous type' to type 'System.String'
Thanks
UPDATE:
At the end I went with:
var result = (from f in db.firmware
where (f.firmware_release_type_text != "")
&& (f.firmware_release_type_text != null)
&& (f.firmware_release_number_int != 0)
select new{
f.firmware_release_type_text,
f.firmware_release_number_int
}
).Distinct();
List<string> result2 = new List<string>();
foreach (var item in result)
{
result2.Add(item.firmware_release_type_text
+ "-" + item.firmware_release_number_int);
}
If you want to return your data as a string you have to say how it should be formatted. You could for example change this:
select new
{
f.firmware_release_type_text , f.firmware_release_number_int
}
To this:
select f.firmware_release_type_text + " v" + (int)f.firmware_release_number_int
You have two options to create a model first, second format the data on the server side.