NEW DBI API (MySQL /SQL)

For scripting examples for learning how to use AMXX.
User avatar
Archive-Poster
Posts: 1267
Gender:
United States of America

NEW DBI API (MySQL /SQL)

Post by Archive-Poster »

This is very important for scripters! For AMXx 0.20, I have significantly changed the SQL interface. All plugins will be rendered broken, so you will need to upgrade.

If you need any help with this, just ask. I can help you convert your plugin or answer technical questions. Remember though, this is for 0.20 and you should be prepared to swap in the new code close to the release date (we will announce this).

This new DBI is important because it gets rid of the shoddy interface from AMX 0.9.x. Plugin writers now get resource handles for both result sets and connections, as well as a better guarantee that the driver will not crash.

To best explain this, here is a sample program:

Code: Select all

//Create a connection
    new Sql:mysql = dbi_connect("localhost", "dvander", "pass", "dbase")

//If the connection is less than 1, it is bad   
    if (mysql < SQL_OK) {
        new err[255]
        new errNum = dbi_error(mysql, err, 254)
        server_print("error1: %s|%d", err, errNum)
        return 1
    }
    
    server_print("Connection handle: %d", mysql)
//Run a query
    new Result:ret = dbi_query(mysql, "INSERT INTO config (keyname, val) VALUES ('amx', 'yes')")

//If the query is less than 0, it failed    
    if (ret < RESULT_NONE) {
        new err[255]
        new errNum = dbi_error(mysql, err, 254)
        server_print("error2: %s|%d", err, errNum)
        return 1
    }

//Do a select query 
    new Result:res = dbi_query(mysql, "SELECT * FROM config")

//If the query is greater than 0, you got a handle to the result set    
    if (res <= RESULT_NONE) {
        new err[255]
        new errNum = dbi_error(mysql, err, 254)
        server_print("error3: %s|%d", err, errNum)
        return 1
    }
    
    server_print("Result handle: %d", res)

//Loop through the result set   
    while (res && dbi_nextrow(res)>0) {
        new qry[32]
//Get the column/field called "keyname" from the result set
        dbi_result(res, "keyname", qry, 32)
        server_print("result: %s", qry)
    }

//Free the result set   
    dbi_free_result(res)

//Close the connection  
    ret = dbi_close(mysql)
    if (ret <= RESULT_NONE) {
        new err[255]
        new errNum = dbi_error(mysql, err, 254)
        server_print("error4: %s|%d", err, errNum)
        return 1
    }
These differences are subtle and important. It means you can create nested or recursive SQL queries and not have the previous ones erased. It also gives you much better errorchecking/debugging control. You can also reference fields by name instead of number.
User avatar
Archive-Poster
Posts: 1267
Gender:
United States of America

Re: NEW DBI API (MySQL /SQL)

Post by Archive-Poster »

//If the query is greater than 0, you got a handle to the result set
if (res <= 0) {
new err[255]
new errNum = dbi_error(mysql, err, 254)
server_print("error3: %s|%d", err, errNum)
return 1
}

server_print("Result handle: %d", res)
What about if the result is an "Empty set"? What is it supposed to return then?
If the result is an empty set, the return value for the result handle (in this program, it is res), will be 0.

That's why I did <0 for INSERT, to check if the insert succeeded, and <=0 for SELECT, to make sure there were items in the table.

Last insert id is a MySQL only feature so I won't add it to the DBI I think. You can do this:
SELECT MAX(field) FROM table
and get the first (only) result.
User avatar
Archive-Poster
Posts: 1267
Gender:
United States of America

Re: NEW DBI API (MySQL /SQL)

Post by Archive-Poster »

A little help, possibly - the person who generally handles any of the mysql plugin modifications and such is out of town on vacation, and I wanted to try to get some of our plugins ported. I just can't quite grasp a couple of the variations:

Here's the entire section of pertinent code from a plugin:

Code: Select all

public read_list(){
  new mhost[64],muser[32],mpass[32],mdb[32],error[128]
  new counter = 0
  new line[48], name[32], steam[32]
  get_cvar_string("amx_mysql_host",mhost,64)
  get_cvar_string("amx_mysql_user",muser,32)
  get_cvar_string("amx_mysql_pass",mpass,32)
  get_cvar_string("amx_mysql_db",mdb,32)
  
  new mysql = mysql_connect(mhost,muser,mpass,mdb,error,127)
  if(mysql < 1){
    server_print("[CLANTAG]: MySQL error: can't connect: '%s'",error)
    return PLUGIN_HANDLED 
  }
  
// Here is where we read from the database. tagprotect is the table.
// steamid and name are varchar fields in the table. You have to create
// the table and fields and populate them for this to work. - Lucky
  if(mysql_query(mysql,"SELECT name,steamid FROM tagprotect") < 1)  {
    mysql_error(mysql,error,127)
    server_print("[CLANTAG]: MySQL error: can't load names and steamids: '%s'",error)
    return PLUGIN_HANDLED
  }

// This loop really isn't quite what it should be, but it works for now - Lucky
  while( mysql_nextrow(mysql) > 0 )
  {
    mysql_getfield(mysql, 1, line ,32)
    if(strtrim(name,line))
    mysql_getfield(mysql, 2, line ,32)
    if(strtrim(steam,line))
    add_clantag(name,steam)
    counter++
  }
  server_print("[CLANTAG]: Loaded %d tag protections.",counter)

  return PLUGIN_CONTINUE
}
I know most of what needs changed in this code in order to convert it to the new dbi interface (amx_sql_*, dbi_connect, etc), but I'm failing to figure out what I need to do with the mysql_getfield entries in the last part, there. I've looked through the other code in this particular plugin, and this is the only part I'm having problems with.

How do dbi_field & dbi_result work? Which would be better to use in this case? Or is a more comprehensive rewrite in order for this usage?
User avatar
Archive-Poster
Posts: 1267
Gender:
United States of America

Re: NEW DBI API (MySQL /SQL)

Post by Archive-Poster »

Rewrite:

Code: Select all

public read_list(){
  new mhost[64],muser[32],mpass[32],mdb[32],error[128]
  new counter = 0
  new line[48], name[32], steam[32]
  get_cvar_string("amx_mysql_host",mhost,64)
  get_cvar_string("amx_mysql_user",muser,32)
  get_cvar_string("amx_mysql_pass",mpass,32)
  get_cvar_string("amx_mysql_db",mdb,32)
  
  new Sql:sql = dbi_connect(mhost,muser,mpass,mdb,error,127)
  if(sql < 1){
    server_print("[CLANTAG]: SQL error: can't connect: '%s'",error)
    return PLUGIN_HANDLED
  }
  
// Here is where we read from the database. tagprotect is the table.
// steamid and name are varchar fields in the table. You have to create
// the table and fields and populate them for this to work. - Lucky
  new Result:res = dbi_query(sql, SELECT name,steamid FROM tagprotect")
  if (res < 0)
    dbi_error(sql,error,127)
    server_print("[CLANTAG]: SQL error: can't load names and steamids: '%s'",error)
    return PLUGIN_HANDLED
  }

// This loop really isn't quite what it should be, but it works for now - Lucky
  while( dbi_nextrow(res) > 0 )
  {
    dbi_field(res, 1, line ,32)
    if(strtrim(name,line))
    dbi_field(res, 2, line ,32)
    if(strtrim(steam,line))
    add_clantag(name,steam)
    counter++
  }
   if (res > 0)
      dbi_free_result(res)
  server_print("[CLANTAG]: Loaded %d tag protections.",counter)

  return PLUGIN_CONTINUE
}
I think that looks right.
The difference between dbi_field and dbi_result is that field gets a result by column number, while dbi_result expects a column name.
User avatar
Archive-Poster
Posts: 1267
Gender:
United States of America

Re: NEW DBI API (MySQL /SQL)

Post by Archive-Poster »

Code: Select all

public read_list(){
  new mhost[64],muser[32],mpass[32],mdb[32],error[128]
  new counter = 0
  new line[48], name[32], steam[32]
  get_cvar_string("amx_sql_host",mhost,64)
  get_cvar_string("amx_sql_user",muser,32)
  get_cvar_string("amx_sql_pass",mpass,32)
  get_cvar_string("amx_sql_db",mdb,32)
  
  new Sql:sql = dbi_connect(mhost,muser,mpass,mdb,error,127)
  if(sql < 1){
    server_print("[CLANTAG]: SQL error: can't connect: '%s'",error)
    return PLUGIN_HANDLED
  }
  
// Here is where we read from the database. tagprotect is the table.
// steamid and name are varchar fields in the table. You have to create
// the table and fields and populate them for this to work. - Lucky
  new Result:res = dbi_query(sql, SELECT name,steamid FROM tagprotect")
  if (res < 0)
    dbi_error(sql,error,127)
    server_print("[CLANTAG]: SQL error: can't load names and steamids: '%s'",error)
    return PLUGIN_HANDLED
  }

// This loop really isn't quite what it should be, but it works for now - Lucky
  while( dbi_nextrow(res) > 0 )
  {
    dbi_field(res, 1, line ,32)
    if(strtrim(name,line))
    dbi_field(res, 2, line ,32)
    if(strtrim(steam,line))
    add_clantag(name,steam)
    counter++
  }
   if (res > 0)
      dbi_free_result(res)
  server_print("[CLANTAG]: Loaded %d tag protections.",counter)

  return PLUGIN_CONTINUE
}
Ah, so you actually can just change mysql_getfield to dbi_field and it would be okay?

And I see where you entered the dbi_free_result . . . those always need to be done AFTER the entire result/lookup? (also, you'll see I changed the amx_mysql_host and such to amx_sql_host . . . is that correct?)
User avatar
Archive-Poster
Posts: 1267
Gender:
United States of America

Re: NEW DBI API (MySQL /SQL)

Post by Archive-Poster »

mysql_getfield -> dbi_field
Not quite, because the first parameter changes from "sql" to "result".

dbi_free_result needs to be called on any result which is greater than 0, after you're finished with it.

Yes, your CVAR changes are correct. Sorry about that.
User avatar
Archive-Poster
Posts: 1267
Gender:
United States of America

Re: NEW DBI API (MySQL /SQL)

Post by Archive-Poster »

If the connection suddenly dies, you will have to close and re-open it. You can check for a dead connection by seeing if a query died when it shouldn't (RESULT_FAILED).
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest