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