shsql.cpp

来自「Shorthand是一个强大的脚本语言」· C++ 代码 · 共 543 行

CPP
543
字号
///////////////////////////////////////////////////////////////////////////////
// $Header: /shorthand/src/shsql.cpp 1     2/14/03 4:20a Arm $
//-----------------------------------------------------------------------------
// Project: ShortHand interpreter
// Author: Andrei Remenchuk <andrei@remenchuk.com>
//-----------------------------------------------------------------------------
// sql.cpp: ShortHand SQL objects and functions
///////////////////////////////////////////////////////////////////////////////
#include <ctype.h>

#include "shsql.h"
#include "nodes.h"
#include "shorthand.h"
#include "module.h"

class ShhDbDriverRegistry
{
private:
    static ShhDbDriverRegistry* m_instance;
    array<ShhDbDriver> m_drivers;
    ShhDbDriverRegistry();
public:
    static ShhDbDriverRegistry* getInstance();
    ShhDbDriver* getDriver(const char* driverName);
};

ShhDbDriverRegistry* ShhDbDriverRegistry::m_instance = NULL;

ShhDbDriverRegistry* ShhDbDriverRegistry::getInstance()
{
    if (m_instance == NULL) m_instance = new ShhDbDriverRegistry();
    return m_instance;
}

ShhDbDriverRegistry::ShhDbDriverRegistry()
{
    extern ShhDbDriver* create_mysql_driver();
    extern ShhDbDriver* create_odbc_driver();

    ShhDbDriver* driver;
    
    driver = create_mysql_driver();
    if (driver != NULL) m_drivers.add(driver);

    driver = create_odbc_driver();
    if (driver != NULL) m_drivers.add(driver);
    
}

ShhDbDriver* ShhDbDriverRegistry::getDriver(const char* driverName)
{
    for(int i=0,n=m_drivers.size(); i<n; i++)
    {
        ShhDbDriver* driver = m_drivers.get(i);
        if (driver != NULL && driver->spi_RecognizeDriverName(driverName))
        {
            return driver;
        }
    }
    return NULL;
}


ShhConnection::ShhConnection(ShhModule& module, const YYLTYPE& location)
: ShhObject(module, "CONNECTION", location)
{
    m_session = NULL;
}

void ShhConnection::constructor(ShhValueList* args)
{
    if (args == NULL || args->size() == 0 || !args->get(0)->isString())
        throw new ShhObjectException("Connection constructor requires driver name as first argument");

    string driverName, connectionString;
    args->get(0)->toString(driverName);

    m_driver = ShhDbDriverRegistry::getInstance()->getDriver(driverName);

    if (m_driver == NULL)
        throw new ShhObjectException("Database driver '%s' is not supported", driverName.cstr());

    if (args->size() < 2 || !args->get(1)->isString()) 
        throw new ShhObjectException("Connection constructor requires connection string as second argument");
    args->get(1)->toString(connectionString);

    // assign read-only properties
    setProperty("DRIVER",     *(args->get(0)) );
    setProperty("SPEC",       *(args->get(1)) );
    
    m_driverName = driverName;
    m_connectionString = connectionString;

    connect();

}

void ShhConnection::connect()
{
    /*
      connection string has the following format:
      "driver={mysql}; database=yourdatabase;server=server;uid=username;pwd=password;option=16386;"
      and is stored in the "STRING" property
    */
    m_session = m_driver->spi_Connect(m_connectionString);
    TRACE((1, "+ ShhConnection::connect(0x%08x)\n", m_session));
}

ShhConnection::~ShhConnection()
{
    disconnect();
}


void ShhConnection::disconnect()
{
    if (m_session != NULL)
    {
        TRACE((1, "- ShhConnection::disconnect(0x%08x)\n", m_session));
        m_driver->spi_Disconnect(m_session);    
    }
}

ShhValue ShhConnection::executeMethod(const char* name, ShhValueList* args)
{
    return ShhValue::EMPTY;
}

bool ShhConnection::hasMethod(const char* name)
{
    return false;
}

ShhDbHandle ShhConnection::provide_session()
{
    if (m_session == NULL)
    {
        connect();
    }
    return m_session;
}

ShhSQL::ShhSQL(ShhModule& module, const char* type, const YYLTYPE& location) 
: ShhObject(module, type, location)
{
    m_connection = NULL;
    m_driver = NULL;
    m_cursor = NULL;
    m_result = NULL;
}

ShhSQL::~ShhSQL()
{
}

ShhDDL::ShhDDL(ShhModule& module, const YYLTYPE& location) 
: ShhSQL(module, "DDL", location)
{
}


ShhRecordSet::ShhRecordSet(ShhModule& module, const YYLTYPE& location) 
: ShhSQL(module, "RECORDSET", location)
{
    m_row = NULL;
    m_field_count = -1;
    m_row_num = 0;
    m_changed_since_execute = false;
    m_no_more_rows = false;
}

ShhRecordSet::~ShhRecordSet()
{
    if (m_driver != NULL)
    {   
        if (m_result != NULL)
            m_driver->spi_CloseResult(m_result);
        if (m_cursor != NULL)
            m_driver->spi_CloseCursor(m_cursor);
    }
}
 

bool ShhSQL::hasMethod(const char* method)
{
    if (stricmp(method, "EXECUTE") == 0)
        return true;
    else
        return false;
}
   
bool ShhRecordSet::hasMethod(const char* method)
{    
    if (ShhSQL::hasMethod(method)) return true;
    
    if (stricmp(method, "COUNT") == 0 
        || stricmp(method, "NEXT") == 0
        || stricmp(method, "EOF") == 0
        || stricmp(method, "MORE") == 0
        || stricmp(method, "ROWNUM") == 0
        || stricmp(method, "VALUE") == 0
        )
    {
        return true;
    }
    else
    {
        return false;
    }
}


void ShhSQL::constructor(ShhValueList* args)
{
    const char* type = getTypeName();

    if (args == NULL || args->size() == 0)
        throw new ShhObjectException(1701, "%s constructor requires Connection argument", type);

    if (!args->get(0)->isObject())
        throw new ShhObjectException(1701, "%s constructor requires Connection object as first argument", type);
    
    ShhObject* arg0 = args->get(0)->toObject();
    if (arg0 == NULL || stricmp(arg0->getTypeName(), "CONNECTION") != 0)
        throw new ShhObjectException(1702, "%s constructor requires Connection object as first argument", type);
    
    if (args->size() < 2 || !args->get(1)->isString()) 
        throw new ShhObjectException("%s constructor requires SQL query string as second argument", type);

    m_connection = (ShhConnection*) arg0;
    m_driver     = m_connection->m_driver;
    setProperty("STATEMENT", *(args->get(1)) );

}

ShhValue ShhSQL::executeMethod(const char* method, ShhValueList* args)
{
    if (stricmp(method, "EXECUTE") == 0)
    {
        return execute(args);
    }
    else
        return ShhValue::null();
}

ShhValue ShhRecordSet::executeMethod(const char* method, ShhValueList* args)
{
    if (stricmp(method, "EXECUTE") == 0)
    {
        // optimize
        if (m_result == NULL || !m_changed_since_execute)
            return execute(args);
        else
            return ShhValue::null();
    }
    else if (stricmp(method, "NEXT") == 0)
        
        return method_next();

    else if (stricmp(method, "MORE") == 0)
        
        return method_more();

    else if (stricmp(method, "ROWNUM") == 0)

        return method_rownum();

    else if (stricmp(method, "COUNT") == 0)

        return method_count();

    else if (stricmp(method, "EOF") == 0)

        return method_finished();

    else if (stricmp(method, "VALUE") == 0)

        return method_value(args);

    else
        throw new ShhObjectException("Object %s doesn't have method \"%s\"", m_type.cstr(), method);
}


void ShhSQL::throwSQL(const char* format, ...)
{
    va_list args; va_start(args, format);
    throw new ShhObjectException(format, args);
}

ShhDbHandle ShhSQL::provide_session()
{
    if (m_connection == NULL)
        throw new ShhObjectException("Connection was not defined for RecordSet object");
    return m_connection->provide_session();
}

const char* ShhSQL::fpi_StoreParameter(int index, const char* value)
{
    string* o = new string(value);
    m_parameters.add(o);
    return o->cstr();
}

ShhDbHandle ShhSQL::fpi_GetConnection()
{
    return m_connection->m_session;
}

void ShhSQL::paste_value(string& out, ShhValue value, int index)
{
    string data;
    
    switch (value.m_type)
    {
    case SHH_INT:    data.printf("%d", value.toInt()); break;
    case SHH_FLOAT:  data.printf("%.15g", value.toFloat()); break;
    case SHH_DATE:   
        {
            string s; datetime dt; value.toDate(dt);
            dt.export_common(s);
            data.printf("%s", s.cstr());
            break;
        }
    case SHH_STRING:
        {
            value.toString(data);
            break;
        }
    
    case SHH_OBJECT:
        {
            const char* type = (value.m_value.u_object != NULL) ? 
                value.m_value.u_object->getTypeName() : "NULL";
            throw new ShhObjectException(2803, "Object <%s> cannot be inserted into SQL. Use explicit conversion");
        }

    default:
        throw new ShhObjectException(2801, "Value of unknown type cannot be pasted into SQL");
    }
    
    m_driver->spi_PasteValue(m_cursor, this, index, out, data);

}

void ShhSQL::substitute(ShhValueList* args, const char* sqlin, string &sqlout)
{
    int argind = 0;
    int argc = args ? args->size() : 0;

    const char* in = sqlin;
    
    char quote = '\0';
    char comment = '\0';
    
    sqlout.clear();
    int paramIndex = 0;
    
    while(*in)
    {
        if (quote != '\0')
        {
            if (*in == '\\') { sqlout.append(*in); in++; }
            else if (*in == quote) quote = '\0';
        }
        else if (*in == '\'' || *in == '\"')
        {
            quote = *in;
        }
        else if (*in == '?')
        {
            if (args == NULL || argind >= argc)
                throw new ShhObjectException(2800, "this SQL requires at least %d parameter(s) - %d passed", argind+1, argc);
            
            paste_value(sqlout, *(args->get(argind)), paramIndex++);
            in++;
            argind++;
            continue;
        }
        else if (*in == ':')
        {
            string name; in++;
            while(*in && (isalnum(*in) || *in == '_')) name.append(*in++);
            
            ShhVariable* var = m_module.findVariable(name);
            if (var == NULL)
                throw new ShhObjectException(2804, "variable '%s' for SQL substitution is not found", name.cstr());
            
            paste_value(sqlout, var->value(), paramIndex++);
            continue;
        }
        sqlout.append(*in);    
        in++;
    }

}


ShhValue ShhSQL::execute(ShhValueList* args)
{
    ShhDbHandle session = provide_session();

    string sqlin, sqlout;

    getProperty("STATEMENT").toString(sqlin);
    if (sqlin.is_empty())
        throw new ShhObjectException("SQL statement is not defined for object");
    
    m_parameters.clear();

    substitute(args, sqlin, sqlout);

    m_cursor = m_driver->spi_PrepareSQL(session, sqlout);
    
    for(int i=0,n=m_parameters.size(); i<n; i++)
    {
        m_driver->spi_BindValue(m_cursor, this, i, m_parameters.get(i)->cstr());
    }
    
    m_result = m_driver->spi_ExecuteCursor(session, m_cursor, sqlout);

    m_driver->spi_ClearParameters(m_cursor);
    m_parameters.clear();
    
    return ShhValue::null();
}


ShhValue ShhRecordSet::execute(ShhValueList* args)
{
    ShhSQL::execute(args);
    m_no_more_rows = false;

    int fieldCount = m_driver->spi_GetFieldCount(m_result);

    if (fieldCount == 0)
    {
        m_field_count = 0;
        m_total_row_count = 0;
        m_row_num = 0;
    } 
    else
    {
        m_field_count = fieldCount;
        m_total_row_count = m_driver->spi_GetRowCount(m_result);
        m_row_num = 1;
    }

    return ShhValue::null();
}

ShhValue ShhRecordSet::method_more()
{
    if (m_row_num < 0)
    {
        return ShhValue::null();
    }
    if (m_no_more_rows) return ShhValue::null();
    return ShhValue(m_row_num < m_total_row_count);
}

ShhValue ShhRecordSet::method_count()
{
    if (m_result == 0)
       throw new ShhObjectException(2807, "Recordset was not executed");

    return ShhValue(m_total_row_count);
}

ShhValue ShhRecordSet::method_finished()
{
    return ShhValue(m_no_more_rows || m_row_num <= 0);
}


ShhValue ShhRecordSet::method_next()
{
    ShhDbHandle session = provide_session();
    m_changed_since_execute = true;

    ShhValue zero(0);

    if (m_result == 0)
       throw new ShhObjectException(2806, "Recordset was not executed");
    
    m_row = m_driver->spi_Fetch(m_result);
    
    if (m_row == 0)
    {
        m_no_more_rows = true;
        return zero;
    }

    if (m_field_count == 0) 
    {
        m_row_num = 0;
        return zero;
    }

    for(int i=0,n=m_field_count; i<n; i++)
    {
        string rawValue, fieldName;
        
        rawValue.clear();
        unsigned int length = m_driver->spi_GetStringValue(m_result, m_row, i, rawValue);
        m_driver->spi_GetFieldName(m_result, i, fieldName);

        ShhValue value("");
        if (length > 0)
        {
            value.m_value.u_string = m_module.scratch().strdup(rawValue);
            value.m_type = SHH_STRING;
        }
        setProperty(fieldName, value);
    }

    m_row_num++;

    return ShhValue(1);

}

ShhValue ShhRecordSet::method_rownum()
{
    ShhDbHandle session = provide_session();
    return ShhValue(m_row_num);
}

ShhValue ShhRecordSet::method_value(ShhValueList* args)
{
    if (args == NULL || args->size() < 1)
    {
        throw new ShhObjectException("method VALUE() needs an argument (field name)");
    }
    
    string name; args->get(0)->toString(name);

    ShhDbHandle session = provide_session();
    return getProperty(name);
}


⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?