461 lines
11 KiB
Plaintext
461 lines
11 KiB
Plaintext
note
|
|
description: "Interface used to implement CMS Storage based on SQL statement."
|
|
date: "$Date: 2015-02-13 13:08:13 +0100 (ven., 13 févr. 2015) $"
|
|
revision: "$Revision: 96616 $"
|
|
|
|
deferred class
|
|
CMS_STORAGE_SQL_I
|
|
|
|
inherit
|
|
SHARED_LOGGER
|
|
|
|
feature -- Access
|
|
|
|
api: detachable CMS_API
|
|
-- Associated CMS api.
|
|
deferred
|
|
end
|
|
|
|
feature -- Error handler
|
|
|
|
error_handler: ERROR_HANDLER
|
|
-- Error handler.
|
|
deferred
|
|
end
|
|
|
|
has_error: BOOLEAN
|
|
-- Last operation reported error.
|
|
do
|
|
Result := error_handler.has_error
|
|
end
|
|
|
|
reset_error
|
|
-- Reset errors.
|
|
do
|
|
error_handler.reset
|
|
end
|
|
|
|
feature -- Execution
|
|
|
|
sql_begin_transaction
|
|
-- Start a database transtaction.
|
|
deferred
|
|
end
|
|
|
|
sql_rollback_transaction
|
|
-- Rollback updates in the database.
|
|
deferred
|
|
end
|
|
|
|
sql_commit_transaction
|
|
-- Commit updates in the database.
|
|
deferred
|
|
end
|
|
|
|
sql_post_execution
|
|
-- Post database execution.
|
|
-- note: execute after each `sql_query' and `sql_change'.
|
|
deferred
|
|
end
|
|
|
|
feature -- Operation
|
|
|
|
check_sql_query_validity (a_sql_statement: READABLE_STRING_8; a_params: detachable STRING_TABLE [detachable ANY])
|
|
local
|
|
l_sql_params: STRING_TABLE [READABLE_STRING_8]
|
|
i,j,n: INTEGER
|
|
s: STRING
|
|
do
|
|
create l_sql_params.make_caseless (0)
|
|
from
|
|
i := 1
|
|
n := a_sql_statement.count
|
|
until
|
|
i > n
|
|
loop
|
|
i := a_sql_statement.index_of (':', i)
|
|
if i = 0 then
|
|
i := n -- exit
|
|
elseif
|
|
a_sql_statement [i-1] = '%''
|
|
or else a_sql_statement [i-1] = '%"'
|
|
or else a_sql_statement [i-1] = ' '
|
|
or else a_sql_statement [i-1] = '='
|
|
or else a_sql_statement [i-1] = '('
|
|
then
|
|
from
|
|
j := i + 1
|
|
until
|
|
j > n or not (a_sql_statement[j].is_alpha_numeric or a_sql_statement[j] = '_')
|
|
loop
|
|
j := j + 1
|
|
end
|
|
s := a_sql_statement.substring (i + 1, j - 1)
|
|
l_sql_params.force (s, s)
|
|
end
|
|
i := i + 1
|
|
end
|
|
if a_params = Void then
|
|
if not l_sql_params.is_empty then
|
|
check False end
|
|
error_handler.add_custom_error (-1, "invalid query", "missing value for sql parameters")
|
|
end
|
|
else
|
|
across
|
|
a_params as ic
|
|
loop
|
|
if l_sql_params.has (ic.key) then
|
|
l_sql_params.remove (ic.key)
|
|
else
|
|
error_handler.add_custom_error (-1, "useless value", "value for unexpected parameter [" + ic.key + "]")
|
|
end
|
|
end
|
|
across
|
|
l_sql_params as ic
|
|
loop
|
|
error_handler.add_custom_error (-1, "invalid query", "missing value for sql parameter [" + ic.item + "]")
|
|
end
|
|
end
|
|
end
|
|
|
|
sql_query (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
|
-- Execute sql query `a_sql_statement' with optional parameters `a_params'.
|
|
deferred
|
|
end
|
|
|
|
sql_finalize
|
|
-- Finalize sql query (i.e destroy previous query statement.
|
|
deferred
|
|
end
|
|
|
|
sql_insert (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
|
-- Execute sql insert `a_sql_statement' with optional parameters `a_params'.
|
|
deferred
|
|
end
|
|
|
|
sql_modify (a_sql_statement: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
|
-- Execute sql modify `a_sql_statement' with optional parameters `a_params'.
|
|
deferred
|
|
end
|
|
|
|
feature -- Helper
|
|
|
|
sql_script_content (a_path: PATH): detachable STRING
|
|
-- Content of sql script located at `a_path'.
|
|
local
|
|
f: PLAIN_TEXT_FILE
|
|
do
|
|
create f.make_with_path (a_path)
|
|
if f.exists and then f.is_access_readable then
|
|
create Result.make (f.count)
|
|
f.open_read
|
|
from
|
|
f.start
|
|
until
|
|
f.exhausted or f.end_of_file
|
|
loop
|
|
f.read_stream_thread_aware (1_024)
|
|
Result.append (f.last_string)
|
|
end
|
|
f.close
|
|
end
|
|
end
|
|
|
|
sql_execute_file_script (a_path: PATH; a_params: detachable STRING_TABLE [detachable ANY])
|
|
-- Execute SQL script from `a_path' and with optional parameters `a_params'.
|
|
do
|
|
if attached sql_script_content (a_path) as sql then
|
|
sql_execute_script (sql, a_params)
|
|
end
|
|
end
|
|
|
|
sql_execute_script (a_sql_script: STRING; a_params: detachable STRING_TABLE [detachable ANY])
|
|
-- Execute SQL script.
|
|
-- i.e: multiple SQL statements.
|
|
local
|
|
i: INTEGER
|
|
err: BOOLEAN
|
|
cl: CELL [INTEGER]
|
|
do
|
|
reset_error
|
|
sql_begin_transaction
|
|
-- Issue on MySQL with multiple statements
|
|
-- sql_change (a_sql_script, Void)
|
|
from
|
|
i := 1
|
|
create cl.put (0)
|
|
until
|
|
i > a_sql_script.count or err
|
|
loop
|
|
if attached next_sql_statement (a_sql_script, i, cl) as s then
|
|
if not s.is_whitespace then
|
|
if s.starts_with ("INSERT") then
|
|
sql_insert (sql_statement (s), a_params)
|
|
else
|
|
sql_modify (sql_statement (s), a_params)
|
|
end
|
|
err := err or has_error
|
|
reset_error
|
|
end
|
|
i := i + cl.item
|
|
else
|
|
i := a_sql_script.count + 1
|
|
end
|
|
end
|
|
if err then
|
|
sql_rollback_transaction
|
|
else
|
|
sql_commit_transaction
|
|
end
|
|
sql_finalize
|
|
end
|
|
|
|
sql_table_exists (a_table_name: READABLE_STRING_8): BOOLEAN
|
|
-- Does table `a_table_name' exists?
|
|
do
|
|
reset_error
|
|
sql_query ("SELECT count(*) FROM " + a_table_name + " ;", Void)
|
|
Result := not has_error
|
|
-- FIXME: find better solution
|
|
sql_finalize
|
|
reset_error
|
|
end
|
|
|
|
sql_table_items_count (a_table_name: READABLE_STRING_8): INTEGER_64
|
|
-- Number of items in table `a_table_name'?
|
|
do
|
|
reset_error
|
|
sql_query ("SELECT count(*) FROM " + a_table_name + " ;", Void)
|
|
if not has_error then
|
|
Result := sql_read_integer_64 (1)
|
|
end
|
|
sql_finalize
|
|
end
|
|
|
|
feature -- Access
|
|
|
|
sql_start
|
|
-- Set the cursor on first element.
|
|
deferred
|
|
end
|
|
|
|
sql_after: BOOLEAN
|
|
-- Are there no more items to iterate over?
|
|
deferred
|
|
end
|
|
|
|
sql_forth
|
|
-- Fetch next row from last sql execution, if any.
|
|
deferred
|
|
end
|
|
|
|
sql_valid_item_index (a_index: INTEGER): BOOLEAN
|
|
deferred
|
|
end
|
|
|
|
sql_item (a_index: INTEGER): detachable ANY
|
|
require
|
|
valid_index: sql_valid_item_index (a_index)
|
|
deferred
|
|
end
|
|
|
|
sql_read_natural_64 (a_index: INTEGER): NATURAL_64
|
|
-- Retrieved value at `a_index' position in `item'.
|
|
local
|
|
l_item: like sql_item
|
|
do
|
|
l_item := sql_item (a_index)
|
|
if attached {NATURAL_64} l_item as i then
|
|
Result := i
|
|
elseif attached {NATURAL_64_REF} l_item as l_value then
|
|
Result := l_value.item
|
|
else
|
|
Result := sql_read_integer_64 (a_index).to_natural_64
|
|
end
|
|
end
|
|
|
|
sql_read_integer_64 (a_index: INTEGER): INTEGER_64
|
|
-- Retrieved value at `a_index' position in `item'.
|
|
local
|
|
l_item: like sql_item
|
|
do
|
|
l_item := sql_item (a_index)
|
|
if attached {INTEGER_64} l_item as i then
|
|
Result := i
|
|
elseif attached {INTEGER_64_REF} l_item as l_value then
|
|
Result := l_value.item
|
|
else
|
|
Result := sql_read_integer_32 (a_index).to_integer_64
|
|
end
|
|
end
|
|
|
|
sql_read_integer_32 (a_index: INTEGER): INTEGER_32
|
|
-- Retrieved value at `a_index' position in `item'.
|
|
deferred
|
|
end
|
|
|
|
sql_read_string (a_index: INTEGER): detachable STRING
|
|
-- Retrieved value at `a_index' position in `item'.
|
|
local
|
|
l_item: like sql_item
|
|
do
|
|
l_item := sql_item (a_index)
|
|
if attached {READABLE_STRING_8} l_item as l_string then
|
|
Result := l_string
|
|
elseif
|
|
attached {READABLE_STRING_32} l_item as l_string_32 and then
|
|
l_string_32.is_valid_as_string_8
|
|
then
|
|
Result := l_string_32.to_string_8
|
|
elseif attached {BOOLEAN} l_item as l_boolean then
|
|
Result := l_boolean.out
|
|
elseif attached {BOOLEAN_REF} l_item as l_boolean_ref then
|
|
Result := l_boolean_ref.item.out
|
|
else
|
|
check is_string_nor_null: l_item = Void end
|
|
end
|
|
end
|
|
|
|
sql_read_string_32 (a_index: INTEGER): detachable STRING_32
|
|
-- Retrieved value at `a_index' position in `item'.
|
|
local
|
|
l_item: like sql_item
|
|
utf: UTF_CONVERTER
|
|
do
|
|
-- FIXME: handle string_32 !
|
|
l_item := sql_item (a_index)
|
|
if attached {READABLE_STRING_32} l_item as l_string then
|
|
Result := l_string
|
|
elseif attached {READABLE_STRING_8} l_item as l_string_8 then
|
|
Result := utf.utf_8_string_8_to_string_32 (l_string_8)
|
|
else
|
|
if attached sql_read_string (a_index) as s8 then
|
|
Result := s8.to_string_32 -- FIXME: any escape?
|
|
else
|
|
check is_string_nor_null: l_item = Void end
|
|
end
|
|
end
|
|
end
|
|
|
|
sql_read_date_time (a_index: INTEGER): detachable DATE_TIME
|
|
-- Retrieved value at `a_index' position in `item'.
|
|
deferred
|
|
end
|
|
|
|
sql_read_boolean (a_index: INTEGER): detachable BOOLEAN
|
|
-- Retrieved value at `a_index' position in `item'.
|
|
local
|
|
l_item: like sql_item
|
|
do
|
|
l_item := sql_item (a_index)
|
|
if attached {BOOLEAN} l_item as l_boolean then
|
|
Result := l_boolean
|
|
elseif attached {BOOLEAN_REF} l_item as l_boolean_ref then
|
|
Result := l_boolean_ref.item
|
|
else
|
|
check is_boolean: False end
|
|
end
|
|
end
|
|
|
|
feature -- Conversion
|
|
|
|
sql_statement (a_statement: STRING): STRING
|
|
-- Statement normalized for underlying SQL database.
|
|
deferred
|
|
end
|
|
|
|
feature {NONE} -- Implementation
|
|
|
|
next_sql_statement (a_script: STRING; a_start_index: INTEGER; a_offset: CELL [INTEGER]): detachable STRING
|
|
local
|
|
i,j,n: INTEGER
|
|
c: CHARACTER
|
|
l_end: INTEGER
|
|
l_removals: detachable ARRAYED_LIST [TUPLE [start_index,end_index: INTEGER]]
|
|
do
|
|
from
|
|
i := a_start_index
|
|
n := a_script.count
|
|
until
|
|
i > n or a_script[i] = ';'
|
|
loop
|
|
c := a_script[i]
|
|
inspect c
|
|
when '-' then
|
|
if i < n and then a_script[i + 1] = '-' then
|
|
-- Commented line "--" until New Line
|
|
j := a_script.index_of ('%N', i)
|
|
if j = 0 then
|
|
j := n
|
|
else
|
|
-- j := j
|
|
end
|
|
if l_removals = Void then
|
|
create l_removals.make (1)
|
|
end
|
|
l_removals.force ([i,j])
|
|
i := j
|
|
end
|
|
when '/' then
|
|
if i < n and then a_script[i + 1] = '*' then
|
|
-- Commented text "/*" until closing "*/"
|
|
j := a_script.substring_index ("*/", i)
|
|
|
|
if j = 0 then
|
|
j := n
|
|
else
|
|
j := j + 1 -- Include '/'
|
|
end
|
|
if l_removals = Void then
|
|
create l_removals.make (1)
|
|
end
|
|
l_removals.force ([i,j])
|
|
i := j
|
|
end
|
|
when '`', '"', '%'' then
|
|
from
|
|
j := i
|
|
l_end := 0
|
|
until
|
|
l_end > 0 or j > n
|
|
loop
|
|
j := a_script.index_of (c, i + 1)
|
|
if j > i then
|
|
if a_script [j - 1] /= '\' then
|
|
l_end := j
|
|
end
|
|
else
|
|
l_end := i
|
|
end
|
|
end
|
|
if l_end > 0 then
|
|
i := l_end
|
|
else
|
|
i := j
|
|
end
|
|
else
|
|
|
|
end
|
|
i := i + 1
|
|
end
|
|
if i <= n and i > a_start_index then
|
|
Result := a_script.substring (a_start_index, i)
|
|
a_offset.replace (Result.count)
|
|
if l_removals /= Void then
|
|
j := 0
|
|
across
|
|
l_removals as ic
|
|
loop
|
|
Result.remove_substring (ic.item.start_index - j, ic.item.end_index - j)
|
|
j := j + ic.item.end_index - ic.item.start_index + 1
|
|
end
|
|
-- a_offset.replace (a_offset.item j)
|
|
end
|
|
end
|
|
end
|
|
|
|
note
|
|
copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
|
end
|