Prepared Statements (#43)

Changed PREPARE syntax to be closer to the standard.
This commit is contained in:
Pedro Flemming 2017-05-29 16:22:13 +02:00 committed by GitHub
parent 5e6cd2d84f
commit f85a5e7b52
14 changed files with 984 additions and 978 deletions

View File

@ -19,14 +19,19 @@ LIBFLAGS = -shared
TARGET = libsqlparser.so
INSTALL = /usr/local
CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++ -O3
CTESTFLAGS = -Wall -Isrc/ -Itest/ -L./ -std=c++11 -lstdc++
# Set compile mode to -g or -O3.
MODE_LOG = ""
mode ?= release
ifeq ($(mode), debug)
CFLAGS += -g
CTESTFLAGS += -g
MODE_LOG = "Building in \033[1;31mdebug\033[0m mode"
else
CFLAGS += -O3
CTESTFLAGS += -O3
MODE_LOG = "Building in \033[0;32mrelease\033[0m mode ('make mode=debug' for debug mode)"
endif
GMAKE = make mode=$(mode)
@ -36,7 +41,6 @@ all: library
library: $(TARGET)
$(TARGET): $(LIBOBJ)
echo $(mode)
$(CXX) $(LIBFLAGS) -o $(TARGET) $(LIBOBJ)
$(SRCPARSER)/flex_lexer.o: $(SRCPARSER)/flex_lexer.cpp $(SRCPARSER)/bison_parser.cpp
@ -108,3 +112,5 @@ format:
astyle --options=astyle.options $(ALLTEST)
astyle --options=astyle.options $(EXAMPLESRC)
log_mode:
@echo $(MODE_LOG)

View File

@ -1,5 +1,6 @@
#include "SQLParserResult.h"
#include <algorithm>
namespace hsql {
@ -95,4 +96,15 @@ namespace hsql {
errorColumn_ = -1;
}
// Does NOT take ownership.
void SQLParserResult::addParameter(Expr* parameter) {
parameters_.push_back(parameter);
std::sort(parameters_.begin(), parameters_.end(),
[](const Expr* a, const Expr* b) { return a->ival < b->ival; });
}
const std::vector<Expr*>& SQLParserResult::parameters() {
return parameters_;
}
} // namespace hsql

View File

@ -63,6 +63,11 @@ namespace hsql {
// Deletes all statements and other data within the result.
void reset();
// Does NOT take ownership.
void addParameter(Expr* parameter);
const std::vector<Expr*>& parameters();
private:
// List of statements within the result.
std::vector<SQLStatement*> statements_;
@ -78,6 +83,9 @@ namespace hsql {
// Column number of the occurrance of the error in the query.
int errorColumn_;
// Does NOT have ownership.
std::vector<Expr*> parameters_;
};
} // namespace hsql

File diff suppressed because it is too large Load Diff

View File

@ -48,7 +48,7 @@
extern int hsql_debug;
#endif
/* "%code requires" blocks. */
#line 36 "bison_parser.y" /* yacc.c:1909 */
#line 34 "bison_parser.y" /* yacc.c:1909 */
// %code requires block
@ -58,18 +58,18 @@ extern int hsql_debug;
// Auto update column and line number
#define YY_USER_ACTION \
yylloc->first_line = yylloc->last_line; \
yylloc->first_column = yylloc->last_column; \
for(int i = 0; yytext[i] != '\0'; i++) { \
yylloc->total_column++; \
if(yytext[i] == '\n') { \
yylloc->last_line++; \
yylloc->last_column = 0; \
} \
else { \
yylloc->last_column++; \
} \
}
yylloc->first_line = yylloc->last_line; \
yylloc->first_column = yylloc->last_column; \
for(int i = 0; yytext[i] != '\0'; i++) { \
yylloc->total_column++; \
if(yytext[i] == '\n') { \
yylloc->last_line++; \
yylloc->last_column = 0; \
} \
else { \
yylloc->last_column++; \
} \
}
#line 75 "bison_parser.h" /* yacc.c:1909 */
@ -214,7 +214,7 @@ extern int hsql_debug;
union HSQL_STYPE
{
#line 95 "bison_parser.y" /* yacc.c:1909 */
#line 92 "bison_parser.y" /* yacc.c:1909 */
double fval;
int64_t ival;

View File

@ -24,8 +24,6 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
return 0;
}
%}
/*********************************
** Section 2: Bison Parser Declarations
@ -42,18 +40,18 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
// Auto update column and line number
#define YY_USER_ACTION \
yylloc->first_line = yylloc->last_line; \
yylloc->first_column = yylloc->last_column; \
for(int i = 0; yytext[i] != '\0'; i++) { \
yylloc->total_column++; \
if(yytext[i] == '\n') { \
yylloc->last_line++; \
yylloc->last_column = 0; \
} \
else { \
yylloc->last_column++; \
} \
}
yylloc->first_line = yylloc->last_line; \
yylloc->first_column = yylloc->last_column; \
for(int i = 0; yytext[i] != '\0'; i++) { \
yylloc->total_column++; \
if(yytext[i] == '\n') { \
yylloc->last_line++; \
yylloc->last_column = 0; \
} \
else { \
yylloc->last_column++; \
} \
}
}
// Define the names of the created files (defined in Makefile)
@ -77,7 +75,6 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
@$.first_line = 0;
@$.last_line = 0;
@$.total_column = 0;
@$.placeholder_id = 0;
};
@ -184,13 +181,13 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
%type <delete_stmt> delete_statement truncate_statement
%type <update_stmt> update_statement
%type <drop_stmt> drop_statement
%type <sval> table_name opt_alias alias file_path
%type <sval> table_name opt_alias alias file_path prepare_target_query
%type <bval> opt_not_exists opt_distinct
%type <uval> import_file_type opt_join_type column_type
%type <table> from_clause table_ref table_ref_atomic table_ref_name
%type <table> join_clause table_ref_name_no_alias
%type <expr> expr operand scalar_expr unary_expr binary_expr logic_expr exists_expr
%type <expr> function_expr between_expr star_expr expr_alias placeholder_expr
%type <expr> function_expr between_expr star_expr expr_alias param_expr
%type <expr> column_name literal int_literal num_literal string_literal
%type <expr> comp_expr opt_where join_condition opt_having case_expr in_expr
%type <limit> opt_limit opt_top
@ -238,11 +235,21 @@ int yyerror(YYLTYPE* llocp, SQLParserResult* result, yyscan_t scanner, const cha
// Defines our general input.
input:
statement_list opt_semicolon {
for (SQLStatement* stmt : *$1) {
// Transfers ownership of the statement.
result->addStatement(stmt);
}
delete $1;
for (SQLStatement* stmt : *$1) {
// Transfers ownership of the statement.
result->addStatement(stmt);
}
unsigned param_id = 0;
for (void* param : yyloc.param_list) {
if (param != nullptr) {
Expr* expr = (Expr*) param;
expr->ival = param_id;
result->addParameter(expr);
++param_id;
}
}
delete $1;
}
;
@ -253,11 +260,7 @@ statement_list:
;
statement:
prepare_statement {
$1->setPlaceholders(yyloc.placeholder_list);
yyloc.placeholder_list.clear();
$$ = $1;
}
prepare_statement
| preparable_statement
;
@ -279,22 +282,15 @@ preparable_statement:
* Prepared Statement
******************************/
prepare_statement:
PREPARE IDENTIFIER ':' preparable_statement {
PREPARE IDENTIFIER FROM prepare_target_query {
$$ = new PrepareStatement();
$$->name = $2;
$$->query = new SQLParserResult($4);
}
| PREPARE IDENTIFIER '{' statement_list opt_semicolon '}' {
$$ = new PrepareStatement();
$$->name = $2;
$$->query = new SQLParserResult();
for (SQLStatement* stmt : *$4) {
$$->query->addStatement(stmt);
}
delete $4;
$$->query = $4;
}
;
prepare_target_query: STRING
execute_statement:
EXECUTE IDENTIFIER {
$$ = new ExecuteStatement();
@ -717,7 +713,7 @@ column_name:
literal:
string_literal
| num_literal
| placeholder_expr
| param_expr
;
string_literal:
@ -738,10 +734,11 @@ star_expr:
'*' { $$ = new Expr(kExprStar); }
;
placeholder_expr:
param_expr:
'?' {
$$ = Expr::makePlaceholder(yylloc.total_column);
yyloc.placeholder_list.push_back($$);
$$ = Expr::makeParameter(yylloc.total_column);
$$->ival2 = yyloc.param_list.size();
yyloc.param_list.push_back($$);
}
;

View File

@ -22,15 +22,12 @@ struct HSQL_CUST_LTYPE {
int total_column;
// Placeholder
int placeholder_id;
std::vector<void*> placeholder_list;
// Parameters.
// int param_id;
std::vector<void*> param_list;
};
#define HSQL_LTYPE HSQL_CUST_LTYPE
#define HSQL_LTYPE_IS_DECLARED 1
#endif

View File

@ -118,8 +118,8 @@ namespace hsql {
return e;
}
Expr* Expr::makePlaceholder(int id) {
Expr* e = new Expr(kExprPlaceholder);
Expr* Expr::makeParameter(int id) {
Expr* e = new Expr(kExprParameter);
e->ival = id;
return e;
}
@ -160,7 +160,7 @@ namespace hsql {
}
bool Expr::isLiteral() const {
return isType(kExprLiteralInt) || isType(kExprLiteralFloat) || isType(kExprLiteralString) || isType(kExprPlaceholder);
return isType(kExprLiteralInt) || isType(kExprLiteralFloat) || isType(kExprLiteralString) || isType(kExprParameter);
}
bool Expr::hasAlias() const {

View File

@ -17,7 +17,7 @@ namespace hsql {
kExprLiteralString,
kExprLiteralInt,
kExprStar,
kExprPlaceholder,
kExprParameter,
kExprColumnRef,
kExprFunctionRef,
kExprOperator,
@ -124,7 +124,7 @@ namespace hsql {
static Expr* makeFunctionRef(char* func_name, std::vector<Expr*>* exprList, bool distinct);
static Expr* makePlaceholder(int id);
static Expr* makeParameter(int id);
static Expr* makeSelect(SelectStatement* select);

View File

@ -0,0 +1,15 @@
#include "PrepareStatement.h"
namespace hsql {
// PrepareStatement
PrepareStatement::PrepareStatement() :
SQLStatement(kStmtPrepare),
name(NULL),
query(NULL) {}
PrepareStatement::~PrepareStatement() {
free(name);
free(query);
}
} // namespace hsql

View File

@ -1,32 +1,20 @@
#ifndef __SQLPARSER__PREPARE_STATEMENT_H__
#define __SQLPARSER__PREPARE_STATEMENT_H__
#include "../SQLParserResult.h"
#include "SQLStatement.h"
#include "SelectStatement.h"
#include <algorithm>
namespace hsql {
// Represents SQL Prepare statements.
// Example: "PREPARE ins_prep: SELECT * FROM t1 WHERE c1 = ? AND c2 = ?"
// Example: PREPARE test FROM 'SELECT * FROM test WHERE a = ?;'
struct PrepareStatement : SQLStatement {
PrepareStatement();
virtual ~PrepareStatement();
// When setting the placeholders we need to make sure that they are in the correct order.
// To ensure that, during parsing we store the character position use that to sort the list here.
void setPlaceholders(std::vector<void*> ph);
char* name;
// The result that is stored within this prepared statement.
SQLParserResult* query;
// The expressions are not owned by this statement.
// Rather they are owned by the query and destroyed, when
// the query is destroyed.
std::vector<Expr*> placeholders;
// The query that is supposed to be prepared.
char* query;
};
} // namsepace hsql

View File

@ -139,29 +139,6 @@ namespace hsql {
}
}
// PrepareStatement
PrepareStatement::PrepareStatement() :
SQLStatement(kStmtPrepare),
name(NULL),
query(NULL) {}
PrepareStatement::~PrepareStatement() {
delete query;
free(name);
}
void PrepareStatement::setPlaceholders(std::vector<void*> ph) {
for (void* e : ph) {
if (e != NULL)
placeholders.push_back((Expr*) e);
}
// Sort by col-id
std::sort(placeholders.begin(), placeholders.end(), [](Expr * i, Expr * j) -> bool { return (i->ival < j->ival); });
// Set the placeholder id on the Expr. This replaces the previously stored column id
for (uintmax_t i = 0; i < placeholders.size(); ++i) placeholders[i]->ival = i;
}
// SelectStatement.h
// OrderDescription

View File

@ -3,7 +3,8 @@
#include "sql_asserts.h"
#include "SQLParser.h"
using hsql::kExprPlaceholder;
using hsql::kExprParameter;
using hsql::kExprLiteralInt;
using hsql::kStmtDrop;
using hsql::kStmtExecute;
@ -21,63 +22,76 @@ using hsql::SelectStatement;
TEST(PrepareSingleStatementTest) {
const std::string query = "PREPARE test: SELECT * FROM students WHERE grade = ?;";
TEST_PARSE_SINGLE_SQL(query, kStmtPrepare, PrepareStatement, result, prepare);
TEST_PARSE_SINGLE_SQL(
"PREPARE test FROM 'SELECT * FROM students WHERE grade = ?';",
kStmtPrepare,
PrepareStatement,
result,
prepare);
const SelectStatement* select = (const SelectStatement*) prepare->query->getStatement(0);
ASSERT_STREQ(prepare->name, "test");
ASSERT_STREQ(prepare->query, "SELECT * FROM students WHERE grade = ?");
TEST_PARSE_SINGLE_SQL(
prepare->query,
kStmtSelect,
SelectStatement,
result2,
select);
ASSERT_EQ(result2.parameters().size(), 1);
ASSERT(select->whereClause->expr2->isType(kExprParameter))
ASSERT_EQ(select->whereClause->expr2->ival, 0)
ASSERT(select->whereClause->isSimpleOp('='));
ASSERT_EQ(select->whereClause->expr2, prepare->placeholders[0])
}
TEST(PrepareMultiStatementTest) {
const std::string query = "PREPARE test {"
"INSERT INTO test VALUES(?);"
"SELECT ?, test FROM test WHERE c1 = ?;"
"};"
"PREPARE stmt: SELECT * FROM data WHERE c1 = ?;"
"DEALLOCATE PREPARE stmt;";
TEST(DeallocatePrepareStatementTest) {
TEST_PARSE_SINGLE_SQL(
"DEALLOCATE PREPARE test;",
kStmtDrop,
DropStatement,
result,
drop);
TEST_PARSE_SQL_QUERY(query, result, 3);
TEST_CAST_STMT(result, 0, kStmtPrepare, PrepareStatement, prep1);
TEST_CAST_STMT(result, 1, kStmtPrepare, PrepareStatement, prep2);
TEST_CAST_STMT(result, 2, kStmtDrop, DropStatement, drop);
// Prepare Statement #1
ASSERT_STREQ(prep1->name, "test");
ASSERT_EQ(prep1->placeholders.size(), 3);
ASSERT_EQ(prep1->query->size(), 2);
TEST_CAST_STMT((*prep1->query), 0, kStmtInsert, InsertStatement, insert);
TEST_CAST_STMT((*prep1->query), 1, kStmtSelect, SelectStatement, select);
ASSERT(insert->values->at(0)->isType(kExprPlaceholder));
ASSERT(select->selectList->at(0)->isType(kExprPlaceholder));
ASSERT(select->whereClause->expr2->isType(kExprPlaceholder));
// Check IDs of placeholders
ASSERT_EQ(insert->values->at(0)->ival, 0);
ASSERT_EQ(insert->values->at(0), prep1->placeholders[0]);
ASSERT_EQ(select->selectList->at(0)->ival, 1);
ASSERT_EQ(select->selectList->at(0), prep1->placeholders[1]);
ASSERT_EQ(select->whereClause->expr2->ival, 2);
ASSERT_EQ(select->whereClause->expr2, prep1->placeholders[2]);
// Prepare Statement #2
ASSERT_STREQ(prep2->name, "stmt");
ASSERT_EQ(prep2->placeholders.size(), 1);
// Deallocate Statement
ASSERT_EQ(drop->type, kDropPreparedStatement);
ASSERT_STREQ(drop->name, "stmt");
ASSERT_STREQ(drop->name, "test");
}
TEST(StatementWithParameters) {
TEST_PARSE_SINGLE_SQL(
"SELECT * FROM test WHERE a = ? AND b = ?",
kStmtSelect,
SelectStatement,
result,
stmt);
const hsql::Expr* eq1 = stmt->whereClause->expr;
const hsql::Expr* eq2 = stmt->whereClause->expr2;
ASSERT_EQ(result.parameters().size(), 2);
ASSERT(eq1->isSimpleOp('='))
ASSERT(eq1->expr->isType(hsql::kExprColumnRef))
ASSERT(eq1->expr2->isType(kExprParameter))
ASSERT_EQ(eq1->expr2->ival, 0)
ASSERT_EQ(result.parameters()[0], eq1->expr2);
ASSERT(eq2->isSimpleOp('='))
ASSERT(eq2->expr->isType(hsql::kExprColumnRef))
ASSERT(eq2->expr2->isType(kExprParameter))
ASSERT_EQ(eq2->expr2->ival, 1)
ASSERT_EQ(result.parameters()[1], eq2->expr2);
}
TEST(ExecuteStatementTest) {
TEST_PARSE_SINGLE_SQL("EXECUTE test(1, 2);", kStmtExecute, ExecuteStatement, result, stmt);
TEST_PARSE_SINGLE_SQL(
"EXECUTE test(1, 2);",
kStmtExecute,
ExecuteStatement,
result,
stmt);
ASSERT_STREQ(stmt->name, "test");
ASSERT_EQ(stmt->parameters->size(), 2);

View File

@ -11,6 +11,7 @@ SELECT * FROM t1 UNION (SELECT * FROM t2 UNION SELECT * FROM t3) ORDER BY col1;
SELECT TOP 10 * FROM t1 ORDER BY col1, col2;
SELECT a, MAX(b), MAX(c, d), CUSTOM(q, UP(r)) AS f FROM t1;
SELECT * FROM t WHERE a BETWEEN 1 and c;
SELECT * FROM t WHERE a = ? AND b = ?;
SELECT City.name, Product.category, SUM(price) FROM fact INNER JOIN City ON fact.city_id = City.id INNER JOIN Product ON fact.product_id = Product.id GROUP BY City.name, Product.category;
# JOIN
SELECT t1.a, t1.b, t2.c FROM "table" AS t1 JOIN (SELECT * FROM foo JOIN bar ON foo.id = bar.id) t2 ON t1.a = t2.b WHERE (t1.b OR NOT t1.a) AND t2.c = 12.5
@ -37,8 +38,8 @@ UPDATE students SET grade = 1.0;
# DROP
DROP TABLE students;
# PREPARE
PREPARE prep_inst: INSERT INTO test VALUES (?, ?, ?);
PREPARE prep2 { INSERT INTO test VALUES (?, 0, 0); INSERT INTO test VALUES (0, ?, 0); INSERT INTO test VALUES (0, 0, ?); };
PREPARE prep_inst FROM 'INSERT INTO test VALUES (?, ?, ?)';
PREPARE prep2 FROM 'INSERT INTO test VALUES (?, 0, 0); INSERT INTO test VALUES (0, ?, 0); INSERT INTO test VALUES (0, 0, ?);';
EXECUTE prep_inst(1, 2, 3);
EXECUTE prep;
DEALLOCATE PREPARE prep;
@ -49,4 +50,5 @@ DEALLOCATE PREPARE prep;
!SELECT abc;
!CREATE TABLE "table" FROM TBL FILE 'students.tbl';SELECT 1
!CREATE TABLE "table" FROM TBL FILE 'students.tbl';1
!INSERT INTO test_table VALUESd (1, 2, 'test');
!INSERT INTO test_table VALUESd (1, 2, 'test');
!SELECT * FROM t WHERE a = ? AND b = ?;SELECT 1;