Changeset 82

Show
Ignore:
Timestamp:
01/30/08 16:41:05 (10 months ago)
Author:
aaronc542
Message:

Added SqlGenerator? helper methods. Added support for DBIException's to MysqlPreparedStatement?.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/dbi/Database.d

    r81 r82  
    88private static import tango.io.Stdout; 
    99private import dbi.DBIException, dbi.Result, dbi.Row, dbi.Statement; 
     10public import dbi.SqlGen; 
    1011 
    1112/** 
     
    205206        return keywords; 
    206207    } 
     208     
     209    static this() 
     210    { 
     211        sqlGen = new SqlGenerator; 
     212    } 
     213    private static SqlGenerator sqlGen; 
     214     
     215    SqlGenerator getSqlGenerator() 
     216    { 
     217        return sqlGen; 
     218    } 
    207219} 
    208220 
  • trunk/dbi/PreparedStatement.d

    r81 r82  
     1/** 
     2 * Authors: The D DBI project 
     3 * Copyright: BSD license 
     4 */ 
    15module dbi.PreparedStatement; 
    26 
     
    1923    void setParamTypes(BindType[] paramTypes); 
    2024    void setResultTypes(BindType[] resTypes); 
    21     bool execute(); 
    22     bool execute(void*[] bind); 
     25    void execute(); 
     26    void execute(void*[] bind); 
    2327    bool fetch(void*[] bind); 
    2428    void prefetchAll(); 
     
    9498    else return BindType.Null; 
    9599} 
     100/+ 
     101class Statement : IPreparedStatement { 
     102    /** 
     103     * Make a new instance of Statement. 
     104     * 
     105     * Params: 
     106     *  database = The database connection to use. 
     107     *  sql = The SQL code to prepare. 
     108     */ 
     109    this (Database database, char[] sql) { 
     110        this.database = database; 
     111        this.sql = sql; 
     112    } 
     113 
     114    /** 
     115     * Execute a SQL statement that returns no results. 
     116     */ 
     117    void execute () { 
     118        database.execute(getSql()); 
     119    } 
     120     
     121    uint getParamCount(); 
     122    FieldInfo[] getResultMetadata(); 
     123    void setParamTypes(BindType[] paramTypes); 
     124    void setResultTypes(BindType[] resTypes); 
     125    void execute(); 
     126    void execute(void*[] bind); 
     127    bool fetch(void*[] bind); 
     128    void prefetchAll(); 
     129    void reset(); 
     130    ulong getLastInsertID(); 
     131    char[] getLastErrorMsg(); 
     132 
     133    /** 
     134     * Query the database. 
     135     * 
     136     * Returns: 
     137     *  A Result object with the queried information. 
     138     */ 
     139    Result query () { 
     140        return database.query(getSql()); 
     141    } 
     142 
     143    private: 
     144    Database database; 
     145    char[] sql; 
     146    char[][] binds; 
     147 
     148    /** 
     149     * Escape a SQL statement. 
     150     * 
     151     * Params: 
     152     *  string = An unescaped SQL statement. 
     153     * 
     154     * Returns: 
     155     *  The escaped form of string. 
     156     */ 
     157    char[] escape (char[] string) { 
     158        if (database !is null) { 
     159            return database.escape(string); 
     160        } else { 
     161            char[] result; 
     162            size_t count = 0; 
     163 
     164            // Maximum length needed if every char is to be quoted 
     165            result.length = string.length * 2; 
     166 
     167            for (size_t i = 0; i < string.length; i++) { 
     168                switch (string[i]) { 
     169                    case '"': 
     170                    case '\'': 
     171                    case '\\': 
     172                        result[count++] = '\\'; 
     173                        break; 
     174                    default: 
     175                        break; 
     176                } 
     177                result[count++] = string[i]; 
     178            } 
     179 
     180            result.length = count; 
     181            return result; 
     182        } 
     183    } 
     184 
     185    /** 
     186     * Replace every "?" in the current SQL statement with its bound value. 
     187     * 
     188     * Returns: 
     189     *  The current SQL statement with all occurences of "?" replaced. 
     190     * 
     191     * Todo: 
     192     *  Raise an exception if binds.length != count(sql, "?") 
     193     */ 
     194    char[] getSqlByQM () { 
     195        char[] result; 
     196        size_t i = 0, j = 0, count = 0; 
     197 
     198        // binds.length is for the '', only 1 because we replace the ? too 
     199        result.length = sql.length + binds.length; 
     200        for (i = 0; i < binds.length; i++) { 
     201            result.length = result.length + binds[i].length; 
     202        } 
     203 
     204        for (i = 0; i < sql.length; i++) { 
     205            if (sql[i] == '?') { 
     206                result[j++] = '\''; 
     207                result[j .. j + binds[count].length] = binds[count]; 
     208                j += binds[count++].length; 
     209                result[j++] = '\''; 
     210            } 
     211            else { 
     212                result[j++] = sql[i]; 
     213            } 
     214        } 
     215 
     216        sql = result; 
     217        return result; 
     218    } 
     219 
     220    /** 
     221     * Replace every ":name:" in the current SQL statement with its bound value. 
     222     * 
     223     * Returns: 
     224     *  The current SQL statement with all occurences of ":name:" replaced. 
     225     * 
     226     * Todo: 
     227     *  Raise an exception if binds.length != (count(sql, ":") * 2) 
     228     */ 
     229    char[] getSqlByFN () { 
     230        char[] result = sql; 
     231        version (Phobos) { 
     232            ptrdiff_t beginIndex = 0, endIndex = 0; 
     233            while ((beginIndex = std.string.find(result, ":")) != -1 && (endIndex = std.string.find(result[beginIndex + 1 .. length], ":")) != -1) { 
     234                result = result[0 .. beginIndex] ~ "'" ~ getBoundValue(result[beginIndex + 1.. beginIndex + endIndex + 1]) ~ "'" ~ result[beginIndex + endIndex + 2 .. length]; 
     235            } 
     236        } else { 
     237            uint beginIndex = 0, endIndex = 0; 
     238            while ((beginIndex = tango.text.Util.locate(result, ':')) != result.length && (endIndex = tango.text.Util.locate(result, ':', beginIndex + 1)) != result.length) { 
     239                result = result[0 .. beginIndex] ~ "'" ~ getBoundValue(result[beginIndex + 1 .. endIndex]) ~ "'" ~ result[endIndex + 1 .. length]; 
     240            } 
     241        } 
     242        return result; 
     243    } 
     244 
     245    /** 
     246     * Replace all variables with their bound values. 
     247     * 
     248     * Returns: 
     249     *  The current SQL statement with all occurences of variables replaced. 
     250     */ 
     251    char[] getSql () { 
     252        version (Phobos) { 
     253            if (std.string.find(sql, "?") != -1) { 
     254                return getSqlByQM(); 
     255            } else if (std.string.find(sql, ":") != -1) { 
     256                return getSqlByFN(); 
     257            } else { 
     258                return sql; 
     259            } 
     260        } else { 
     261            if (tango.text.Util.contains(sql, '?')) { 
     262                return getSqlByQM(); 
     263            } else if (tango.text.Util.contains(sql, ':')) { 
     264                return getSqlByFN(); 
     265            } else { 
     266                return sql; 
     267            } 
     268        } 
     269    } 
     270 
     271    /** 
     272     * Get the value bound to a ":name:". 
     273     * 
     274     * Params: 
     275     *  fn = The ":name:" to return the bound value of. 
     276     * 
     277     * Returns: 
     278     *  The bound value of fn. 
     279     * 
     280     * Throws: 
     281     *  DBIException if fn is not bound 
     282     */ 
     283    char[] getBoundValue (char[] fn) { 
     284        for (size_t index = 0; index < bindsFNs.length; index++) { 
     285            if (bindsFNs[index] == fn) { 
     286                return binds[index]; 
     287            } 
     288        } 
     289        throw new DBIException(fn ~ " is not bound in the Statement."); 
     290    } 
     291} 
     292 
     293debug(UnitTest) { 
     294unittest { 
     295    version (Phobos) { 
     296        void s1 (char[] s) { 
     297            std.stdio.writefln("%s", s); 
     298        } 
     299 
     300        void s2 (char[] s) { 
     301            std.stdio.writefln("   ...%s", s); 
     302        } 
     303    } else { 
     304        void s1 (char[] s) { 
     305            tango.io.Stdout.Stdout(s).newline(); 
     306        } 
     307 
     308        void s2 (char[] s) { 
     309            tango.io.Stdout.Stdout("   ..." ~ s).newline(); 
     310        } 
     311    } 
     312 
     313    s1("dbi.Statement:"); 
     314    Statement stmt = new Statement(null, "SELECT * FROM people"); 
     315    char[] resultingSql = "SELECT * FROM people WHERE id = '10' OR name LIKE 'John Mc\\'Donald'"; 
     316 
     317    s2("escape"); 
     318    assert (stmt.escape("John Mc'Donald") == "John Mc\\'Donald"); 
     319 
     320    s2("simple sql"); 
     321    stmt = new Statement(null, "SELECT * FROM people"); 
     322    assert (stmt.getSql() == "SELECT * FROM people"); 
     323 
     324    s2("bind by '?'"); 
     325    stmt = new Statement(null, "SELECT * FROM people WHERE id = ? OR name LIKE ?"); 
     326    stmt.bind(1, "10"); 
     327    stmt.bind(2, "John Mc'Donald"); 
     328    assert (stmt.getSql() == resultingSql); 
     329 
     330    /+ 
     331    s2("bind by '?' sent to getSql via variable arguments"); 
     332    stmt = new Statement("SELECT * FROM people WHERE id = ? OR name LIKE ?"); 
     333    assert (stmt.getSql("10", "John Mc'Donald") == resultingSql); 
     334    +/ 
     335 
     336    s2("bind by ':fieldname:'"); 
     337    stmt = new Statement(null, "SELECT * FROM people WHERE id = :id: OR name LIKE :name:"); 
     338    stmt.bind("id", "10"); 
     339    stmt.bind("name", "John Mc'Donald"); 
     340    assert (stmt.getBoundValue("name") == "John Mc\\'Donald"); 
     341    assert (stmt.getSql() == resultingSql); 
     342} 
     343} 
     344+/ 
  • trunk/dbi/mysql/MysqlDatabase.d

    r81 r82  
    235235                return mysql_insert_id(connection); 
    236236        } 
     237         
     238    static this() 
     239    { 
     240        mysqlSqlGen = new MysqlSqlGenerator; 
     241    } 
     242    private static MysqlSqlGenerator mysqlSqlGen; 
     243         
     244    override SqlGenerator getSqlGenerator() 
     245    { 
     246        return mysqlSqlGen; 
     247    } 
    237248 
    238249    package: 
    239250    MYSQL* connection; 
     251} 
     252 
     253class MysqlSqlGenerator : SqlGenerator 
     254{ 
     255    override char getIdentifierQuoteCharacter() 
     256    { 
     257        return '`';  
     258    } 
    240259} 
    241260 
     
    267286        tango.io.Stdout.Stdout("   ..." ~ s).newline(); 
    268287    } 
    269 /+ 
     288 
    270289    s1("dbi.mysql.MysqlDatabase:"); 
    271290    MysqlDatabase db = new MysqlDatabase(); 
    272   s2("connect"); 
     291/+    s2("connect"); 
    273292    db.connect("dbname=test", "test", "test"); 
    274293 
     
    313332    s2("close"); 
    314333    db.close();+/ 
    315 
    316 
    317  
    318 
     334    auto sqlgen = db.getSqlGenerator; 
     335    auto res = sqlgen.makeInsertSql("user", ["name", "date"]); 
     336    assert(res == "INSERT INTO `user` (`name`,`date`) VALUES(?,?)", res); 
     337
     338
     339 
     340
  • trunk/dbi/mysql/MysqlPreparedStatement.d

    r81 r82  
    1818     
    1919import dbi.mysql.MysqlDatabase; 
     20import dbi.DBIException, dbi.mysql.MysqlError; 
    2021version(Windows) { 
    2122    private import dbi.mysql.imp_win; 
     
    3031    this(MysqlDatabase db) 
    3132    { 
    32         if(!db.connection) throw new Exception("Attempting to create prepared statements but not connected to database"); 
     33        if(!db.connection) throw new DBIException("Attempting to create prepared statements but not connected to database"); 
    3334        mysql = db.connection; 
    3435    } 
     
    4546                log.error("Unable to create prepared statement: \"" ~ sql ~"\", errmsg: " ~ toDString(err)); 
    4647            } 
    47             return null; 
     48            //return null; 
     49            auto errno = mysql_stmt_errno(stmt); 
     50            throw new DBIException("Unable to prepare statement: " ~ sql, errno, specificToGeneral(errno)); 
    4851        } 
    4952        return new MysqlPreparedStatement(stmt); 
     
    172175    } 
    173176     
    174     bool execute() 
    175     { 
    176         return mysql_stmt_execute(stmt) == 0 ? true : false; 
    177     } 
    178      
    179     bool execute(void*[] bind) 
    180     { 
    181         if(!bind || !paramBind) throw new Exception("Attempting to execute a statement without having set parameters types or based a valid bind array."); 
    182         if(bind.length != paramBind.length) throw new Exception("Incorrect number of pointers in bind array"); 
     177    void execute() 
     178    { 
     179        auto res = mysql_stmt_execute(stmt); 
     180        if(res != 0) { 
     181            throw new DBIException("Error at mysql_stmt_execute.", res, specificToGeneral(res)); 
     182        } 
     183    } 
     184     
     185    void execute(void*[] bind) 
     186    { 
     187        if(!bind || !paramBind) throw new DBIException("Attempting to execute a statement without having set parameters types or passed a valid bind array."); 
     188        if(bind.length != paramBind.length) throw new DBIException("Incorrect number of pointers in bind array"); 
    183189         
    184190        uint len = bind.length; 
     
    221227         
    222228        auto res = mysql_stmt_bind_param(stmt, paramBind.ptr); 
    223         if(res != 0) return false; 
     229        if(res != 0) { 
     230            throw new DBIException("Error at mysql_stmt_bind_param.", res, specificToGeneral(res)); 
     231        } 
    224232        res = mysql_stmt_execute(stmt); 
    225         return res == 0 ? true : false; 
     233        if(res != 0) { 
     234            throw new DBIException("Error at mysql_stmt_execute.", res, specificToGeneral(res)); 
     235        } 
    226236    } 
    227237     
    228238    bool fetch(void*[] bind) 
    229239    { 
    230         if(!bind || !resBind) throw new Exception("Attempting to fetch from a statement without having set parameters types or based a valid bind array."); 
    231         if(bind.length != resBind.length) throw new Exception("Incorrect number of pointers in bind array"); 
     240        if(!bind || !resBind) throw new DBIException("Attempting to fetch from a statement without having set parameters types or passed a valid bind array."); 
     241        if(bind.length != resBind.length) throw new DBIException("Incorrect number of pointers in bind array"); 
    232242         
    233243        uint len = bind.length; 
     
    607617    bind[1] = &name; 
    608618    bind[2] = &dateofbirth; 
    609     assert(st.execute)
     619    st.execute
    610620    assert(st.fetch(bind)); 
    611621    Stdout.formatln("id:{},name:{},dateofbirth:{}",id,name,dateofbirth.ticks); 
     
    621631    st2.setResultTypes(resTypes); 
    622632    pBind ~= &usID; 
    623     assert(st2.execute(pBind)); 
     633    st2.execute(pBind); 
    624634    assert(st2.fetch(bind)); 
    625635    Stdout.formatln("id:{},name:{},dateofbirth:{}",id,name,dateofbirth.ticks);