update all indirect references to native strings
[nit.git] / lib / sqlite3 / sqlite3.nit
index f4e855d..43af40d 100644 (file)
@@ -20,7 +20,7 @@
 module sqlite3
 
 private import native_sqlite3
-import standard
+import core
 
 # A connection to a Sqlite3 database
 class Sqlite3DB
@@ -35,13 +35,15 @@ class Sqlite3DB
        # Open a connection to the database file at `path`
        init open(path: Text)
        do
-               native_connection = new NativeSqlite3.open(path.to_s)
+               init(new NativeSqlite3.open(path.to_cstring))
                if native_connection.is_valid then is_open = true
        end
 
        # Close this connection to the DB and all open statements
        fun close
        do
+               if not is_open then return
+
                is_open = false
 
                # close open statements
@@ -55,8 +57,8 @@ class Sqlite3DB
        # Prepare and return a `Statement`, return `null` on error
        fun prepare(sql: Text): nullable Statement
        do
-               var native_stmt = native_connection.prepare(sql.to_s)
-               if native_stmt == null then return null
+               var native_stmt = native_connection.prepare(sql.to_cstring)
+               if native_stmt.address_is_null then return null
 
                var stmt = new Statement(native_stmt)
                open_statements.add stmt
@@ -66,7 +68,7 @@ class Sqlite3DB
        # Execute the `sql` statement and return `true` on success
        fun execute(sql: Text): Bool
        do
-               var err = native_connection.exec(sql.to_s)
+               var err = native_connection.exec(sql.to_cstring)
                return err.is_ok
        end
 
@@ -95,24 +97,47 @@ class Sqlite3DB
        # The latest error message, or `null` if there is none
        fun error: nullable String
        do
+               if not native_connection.is_valid then
+                       var err = sys.sqlite_open_error
+                       if err.is_ok then return null
+                       return err.to_s
+               end
+
                var err = native_connection.error
                if err.is_ok then return null
                return err.to_s
        end
+
+       # Returns the id for the last successful insert on the current connection.
+       fun last_insert_rowid: Int do return native_connection.last_insert_rowid
 end
 
-# A prepared Sqlite3 statement, created from `Sqlite3DB::prepare` or `Sqlite3DB::select`
+# Prepared Sqlite3 statement
+#
+# Instances of this class are created from `Sqlite3DB::prepare` and
+# its shortcuts: `create_table`, `insert`, `replace` and `select`.
+# The results should be explored with an `iterator`,
+# and each call to `iterator` resets the request.
+# If `close_with_iterator` the iterator calls `close`
+# on this request upon finishing.
 class Statement
        private var native_statement: NativeStatement
 
-       private init(ns: NativeStatement) do self.native_statement = ns
-
        # Is this statement usable?
        var is_open = true
 
+       # Should any `iterator` close this statement on `Iterator::finish`?
+       #
+       # If `true`, the default, any `StatementIterator` created by calls to
+       # `iterator` invokes `close` on this request when finished iterating.
+       # Otherwise, `close` must be called manually.
+       var close_with_iterator = true is writable
+
        # Close and finalize this statement
        fun close
        do
+               if not is_open then return
+
                is_open = false
                native_statement.finalize
        end
@@ -121,16 +146,25 @@ class Statement
        fun iterator: StatementIterator
        do
                native_statement.reset
-               native_statement.step
                return new StatementIterator(self)
        end
 end
 
+# A row from a `Statement`
 class StatementRow
        # Statement linked to `self`
        var statement: Statement
 
-       private init(s: Statement) do self.statement = s
+       # Maps the column name to its value
+       fun map: Map[String, nullable Sqlite3Data]
+       do
+               var ret = new ArrayMap[String, nullable Sqlite3Data]
+               for i in [0 .. length[ do
+                       var st = self[i]
+                       ret[st.name] = st.value
+               end
+               return ret
+       end
 
        # Number of entries in this row
        #
@@ -153,19 +187,15 @@ class StatementEntry
 
        private var index: Int
 
-       private init(s: Statement, i: Int)
-       do
-               self.statement = s
-               self.index = i
-       end
-
        # Name of the column
        #
        # require: `self.statement.is_open`
-       fun name: String is cached do
+       var name: String is lazy do
                assert statement_closed: statement.is_open
 
-               return statement.native_statement.column_name(index)
+               var cname = statement.native_statement.column_name(index)
+               assert not cname.address_is_null
+               return cname.to_s
        end
 
        # Get the value of this entry according to its Sqlite type
@@ -220,9 +250,9 @@ class StatementEntry
        do
                assert statement_closed: statement.is_open
 
-               var native_string = statement.native_statement.column_text(index)
-               if native_string.address_is_null then return ""
-               return native_string.to_s
+               var c_string = statement.native_statement.column_text(index)
+               if c_string.address_is_null then return ""
+               return c_string.to_s_with_copy
        end
 
        # Get this entry as `Blob`
@@ -250,15 +280,15 @@ class StatementIterator
        # Statement linked to `self`
        var statement: Statement
 
-       private init(s: Statement)
+       init
        do
-               self.statement = s
-               self.item = new StatementRow(s)
+               self.item = new StatementRow(statement)
+               self.is_ok = statement.native_statement.step.is_row
        end
 
-       redef var item: StatementRow
+       redef var item: StatementRow is noinit
 
-       redef var is_ok = true
+       redef var is_ok is noinit
 
        # require: `self.statement.is_open`
        redef fun next
@@ -277,25 +307,55 @@ class StatementIterator
                        is_ok = false
                end
        end
+
+       redef fun finish do if statement.close_with_iterator then statement.close
 end
 
 # A data type supported by Sqlite3
 interface Sqlite3Data end
 
 redef universal Int super Sqlite3Data end
+
 redef universal Float super Sqlite3Data end
+
 redef class String super Sqlite3Data end
 
-# A Sqlite3 blob
-class Blob
-       super Sqlite3Data
+redef class Text
 
-       private init(pointer: Pointer, length: Int)
+       # Return `self` between `'`s, escaping `\` and `'`
+       #
+       #     assert "'; DROP TABLE students".to_sql_string == "'''; DROP TABLE students'"
+       fun to_sql_string: String
        do
-               self.pointer = pointer
-               self.length = length
+               return "'{self.replace('\\', "\\\\").replace('\'', "''")}'"
        end
 
+       # Format the date represented by `self` into an escaped string for SQLite
+       #
+       # `self` must be composed of 1 to 3 integers separated by '-'.
+       # An incompatible format will result in an invalid date string.
+       #
+       #     assert "2016-5-1".to_sql_date_string == "'2016-05-01'"
+       #     assert "2016".to_sql_date_string == "'2016-01-01'"
+       fun to_sql_date_string: String
+       do
+               var parts = self.split("-")
+               for i in [parts.length .. 3[ do parts[i] = "1"
+
+               var year = parts[0].justify(4, 1.0, '0')
+               var month = parts[1].justify(2, 1.0, '0')
+               var day = parts[2].justify(2, 1.0, '0')
+               return "{year}-{month}-{day}".to_sql_string
+       end
+end
+
+# A Sqlite3 blob
+class Blob
+       super Sqlite3Data
+
+       # Pointer to the beginning of the blob
        var pointer: Pointer
+
+       # Size of the blob
        var length: Int
 end