mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-10-31 06:02:02 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			783 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			783 lines
		
	
	
		
			30 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
| unit sqlitejniandroid;
 | |
| 
 | |
| {
 | |
|   This is TSqlite3Dataset, a TDataset descendant class for use with fpc compiler
 | |
|   Copyright (C) 2004  Luiz Américo Pereira Câmara
 | |
|   Email: pascalive@bol.com.br
 | |
| 
 | |
|   This library is free software; you can redistribute it and/or modify it
 | |
|   under the terms of the GNU Library General Public License as published by
 | |
|   the Free Software Foundation; either version 2 of the License, or (at your
 | |
|   option) any later version with the following modification:
 | |
| 
 | |
|   As a special exception, the copyright holders of this library give you
 | |
|   permission to link this library with independent modules to produce an
 | |
|   executable, regardless of the license terms of these independent modules,and
 | |
|   to copy and distribute the resulting executable under terms of your choice,
 | |
|   provided that you also meet, for each linked independent module, the terms
 | |
|   and conditions of the license of that module. An independent module is a
 | |
|   module which is not derived from or based on this library. If you modify
 | |
|   this library, you may extend this exception to your version of the library,
 | |
|   but you are not obligated to do so. If you do not wish to do so, delete this
 | |
|   exception statement from your version.
 | |
| 
 | |
|   This program is distributed in the hope that it will be useful, but WITHOUT
 | |
|   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | |
|   FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
 | |
|   for more details.
 | |
| 
 | |
|   You should have received a copy of the GNU Library General Public License
 | |
|   along with this library; if not, write to the Free Software Foundation,
 | |
|   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 | |
| }
 | |
| 
 | |
| {$mode objfpc}
 | |
| {$H+}
 | |
| {.$Define DEBUG_SQLITEDS}
 | |
| 
 | |
| interface
 | |
| 
 | |
| uses
 | |
|   Classes, SysUtils, CustomSqliteDS,
 | |
|   jni, customdrawnint;
 | |
| 
 | |
| type
 | |
|   { TSqliteJNIDataset }
 | |
| 
 | |
|   TSqliteJNIDataset = class(TCustomSqliteDataset)
 | |
|   private
 | |
|     FLastInsertRowId: Int64;
 | |
|     FReturnString: string;
 | |
|     // Java Classes
 | |
|     FSqliteClosableClass, FSQLiteDatabaseClass, FDBCursorClass: JClass;
 | |
|     // Java Methods
 | |
|     FSqliteClosable_acquireReference, FSqliteClosable_releaseReference: JMethodID;
 | |
|     FSqliteDatabase_ExecSQLMethod, FSqliteDatabase_openOrCreateDatabase,
 | |
|       FSqliteDatabase_getVersion, FSqliteDatabase_query,
 | |
|       FSqliteDatabase_execSQL, FSqliteDatabase_rawQuery,
 | |
|       FSqliteDatabase_inTransaction: JMethodID;
 | |
|     FDBCursor_getColumnCount, FDBCursor_getColumnName, FDBCursor_getType,
 | |
|       FDBCursor_close, FDBCursor_getCount, FDBCursor_getDouble,
 | |
|       FDBCursor_getLong, FDBCursor_getPosition, FDBCursor_getString,
 | |
|       FDBCursor_moveToFirst, FDBCursor_moveToNext, FDBCursor_moveToPosition,
 | |
|       FDBCursor_moveToPrevious: JMethodID;
 | |
|     // Java Objects
 | |
|     AndroidDB: jobject; // SQLiteDatabase
 | |
|     function  SplitSQLStatements(AInput: string): TStrings;
 | |
|     procedure FindJavaClassesAndMethods;
 | |
|     procedure RealInternalCloseHandle;
 | |
|     function  RealInternalGetHandle: Pointer;
 | |
|   protected
 | |
|     procedure BuildLinkedList; override;
 | |
|     function  GetLastInsertRowId: Int64; override;
 | |
|     function  GetRowsAffected:Integer; override;
 | |
|     procedure InternalCloseHandle; override;
 | |
|     function  InternalGetHandle: Pointer; override;
 | |
|     procedure RetrieveFieldDefs; override;
 | |
|     function  SqliteExec(ASQL: PChar; ACallback: TSqliteCdeclCallback; Data: Pointer): Integer; override;
 | |
|     function  CheckJNIException(ARaisePascalException: Boolean): Boolean;
 | |
|   public
 | |
|     procedure ExecuteDirect(const ASQL: String); override;
 | |
|     function  QuickQuery(const ASQL: String; const AStrList: TStrings; FillObjects: Boolean): String; override;
 | |
|     function  ReturnString: String; override;
 | |
|     class function SqliteVersion: String; override;
 | |
|   end;
 | |
| 
 | |
| implementation
 | |
| 
 | |
| uses
 | |
|   db, strutils, lclproc;
 | |
| 
 | |
| const
 | |
|   //
 | |
|   // from android.database.Cursor
 | |
|   //
 | |
|   FIELD_TYPE_BLOB = 4;   // Added in API level 11
 | |
|   FIELD_TYPE_FLOAT = 2;  // Added in API level 11
 | |
|   FIELD_TYPE_INTEGER = 1;// Added in API level 11
 | |
|   FIELD_TYPE_NULL = 0;   // Added in API level 11
 | |
|   FIELD_TYPE_STRING = 3; // Added in API level 11
 | |
| 
 | |
| {
 | |
| Java code example
 | |
| Docs: http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
 | |
| 
 | |
| SQLiteDatabase db = openOrCreateDatabase(Preferences.DB_NAME, Context.MODE_PRIVATE, null);
 | |
| db.execSQL("CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY, value VARCHAR)");
 | |
| Cursor dbResult = db.rawQuery("SELECT value FROM data", null);
 | |
| // do sometning with cursors
 | |
| dbResult.close();
 | |
| db.close(); // releaseReference(); is the same
 | |
| }
 | |
| 
 | |
| function SqliteCode2Str(Code: Integer): String;
 | |
| begin
 | |
|   {case Code of
 | |
|     SQLITE_OK           :} Result := 'SQLITE_OK';
 | |
|     {SQLITE_ERROR        : Result := 'SQLITE_ERROR';
 | |
|     SQLITE_INTERNAL     : Result := 'SQLITE_INTERNAL';
 | |
|     SQLITE_PERM         : Result := 'SQLITE_PERM';
 | |
|     SQLITE_ABORT        : Result := 'SQLITE_ABORT';
 | |
|     SQLITE_BUSY         : Result := 'SQLITE_BUSY';
 | |
|     SQLITE_LOCKED       : Result := 'SQLITE_LOCKED';
 | |
|     SQLITE_NOMEM        : Result := 'SQLITE_NOMEM';
 | |
|     SQLITE_READONLY     : Result := 'SQLITE_READONLY';
 | |
|     SQLITE_INTERRUPT    : Result := 'SQLITE_INTERRUPT';
 | |
|     SQLITE_IOERR        : Result := 'SQLITE_IOERR';
 | |
|     SQLITE_CORRUPT      : Result := 'SQLITE_CORRUPT';
 | |
|     SQLITE_NOTFOUND     : Result := 'SQLITE_NOTFOUND';
 | |
|     SQLITE_FULL         : Result := 'SQLITE_FULL';
 | |
|     SQLITE_CANTOPEN     : Result := 'SQLITE_CANTOPEN';
 | |
|     SQLITE_PROTOCOL     : Result := 'SQLITE_PROTOCOL';
 | |
|     SQLITE_EMPTY        : Result := 'SQLITE_EMPTY';
 | |
|     SQLITE_SCHEMA       : Result := 'SQLITE_SCHEMA';
 | |
|     SQLITE_TOOBIG       : Result := 'SQLITE_TOOBIG';
 | |
|     SQLITE_CONSTRAINT   : Result := 'SQLITE_CONSTRAINT';
 | |
|     SQLITE_MISMATCH     : Result := 'SQLITE_MISMATCH';
 | |
|     SQLITE_MISUSE       : Result := 'SQLITE_MISUSE';
 | |
|     SQLITE_NOLFS        : Result := 'SQLITE_NOLFS';
 | |
|     SQLITE_AUTH         : Result := 'SQLITE_AUTH';
 | |
|     SQLITE_FORMAT       : Result := 'SQLITE_FORMAT';
 | |
|     SQLITE_RANGE        : Result := 'SQLITE_RANGE';
 | |
|     SQLITE_ROW          : Result := 'SQLITE_ROW';
 | |
|     SQLITE_NOTADB       : Result := 'SQLITE_NOTADB';
 | |
|     SQLITE_DONE         : Result := 'SQLITE_DONE';
 | |
|   else
 | |
|     Result := 'Unknown Return Value';
 | |
|   end;     }
 | |
| end;
 | |
| 
 | |
| function GetAutoIncValue(NextValue: Pointer; Columns: Integer; ColumnValues: PPChar; ColumnNames: PPChar): Integer; cdecl;
 | |
| var
 | |
|   CodeError, TempInt: Integer;
 | |
| begin
 | |
|   TempInt := 0;
 | |
|   if ColumnValues[0] <> nil then
 | |
|   begin
 | |
|     Val(String(ColumnValues[0]), TempInt, CodeError);
 | |
|     if CodeError <> 0 then
 | |
|       DatabaseError('TSqlite3Dataset: Error trying to get last autoinc value');
 | |
|   end;
 | |
|   Integer(NextValue^) := Succ(TempInt);
 | |
|   Result := 1;
 | |
| end;
 | |
| 
 | |
| { TSqlite3Dataset }
 | |
| 
 | |
| function TSqliteJNIDataset.SplitSQLStatements(AInput: string): TStrings;
 | |
| var
 | |
|   i: Integer;
 | |
|   CurChar: Char;
 | |
|   CurStr: string;
 | |
|   InQuote: Boolean = false;
 | |
| begin
 | |
|   Result := TStringList.Create;
 | |
|   for i := 1 to Length(AInput) do
 | |
|   begin
 | |
|     CurChar := AInput[i];
 | |
| 
 | |
|     if CurChar = '''' then InQuote := not InQuote;
 | |
| 
 | |
|     // Add a statement when it finishes with a ; outside quotes
 | |
|     if (CurChar = ';') and (not InQuote) then
 | |
|     begin
 | |
|       Result.Add(CurStr);
 | |
|       CurStr := '';
 | |
|     end
 | |
|     else
 | |
|       CurStr := CurStr + CurChar;
 | |
|   end;
 | |
|   // add the last statement even if it doesn't end with a ;
 | |
|   if CurStr <> '' then Result.Add(CurStr);
 | |
| end;
 | |
| 
 | |
| procedure TSqliteJNIDataset.FindJavaClassesAndMethods;
 | |
| begin
 | |
|   FSQLiteDatabaseClass := javaEnvRef^^.FindClass(javaEnvRef, 'android/database/sqlite/SQLiteDatabase');
 | |
|   FSQLiteClosableClass := javaEnvRef^^.FindClass(javaEnvRef, 'android/database/sqlite/SQLiteClosable');
 | |
|   FDBCursorClass := javaEnvRef^^.FindClass(javaEnvRef, 'android/database/Cursor');
 | |
| 
 | |
|   //
 | |
|   // Methods from SqliteDatabase
 | |
|   //
 | |
|   FSqliteDatabase_ExecSQLMethod := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteDatabaseClass, 'execSQL',
 | |
|     '(Ljava/lang/String;)V');
 | |
|   FSqliteDatabase_openOrCreateDatabase := javaEnvRef^^.GetStaticMethodID(javaEnvRef, FSQLiteDatabaseClass, 'openOrCreateDatabase',
 | |
|     '(Ljava/lang/String;Landroid/database/sqlite/SQLiteDatabase$CursorFactory;)Landroid/database/sqlite/SQLiteDatabase;');
 | |
|   //DebugLn('[TSqliteJNIDataset.FindJavaClassesAndMethods] FSqliteDatabase_openOrCreateDatabase='+IntToHex(Cardinal(FSqliteDatabase_openOrCreateDatabase), 8));
 | |
|   FSqliteDatabase_getVersion := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteDatabaseClass, 'getVersion',
 | |
|     '()I');
 | |
|   // public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
 | |
|   FSqliteDatabase_query := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteDatabaseClass, 'query',
 | |
|     '(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;');
 | |
|   // void execSQL(String sql)
 | |
|   FSqliteDatabase_execSQL := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteDatabaseClass, 'execSQL',
 | |
|     '(Ljava/lang/String;)V');
 | |
|   // public Cursor rawQuery (String sql, String[] selectionArgs)
 | |
|   FSqliteDatabase_rawQuery := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteDatabaseClass, 'rawQuery',
 | |
|     '(Ljava/lang/String;[Ljava/lang/String;)Landroid/database/Cursor;');
 | |
|   // public boolean inTransaction ()
 | |
|   FSqliteDatabase_inTransaction := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteDatabaseClass, 'inTransaction',
 | |
|     '()Z');
 | |
|   //
 | |
|   // Methods from FDBClosable
 | |
|   //
 | |
|   FSqliteClosable_acquireReference := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteClosableClass, 'acquireReference',
 | |
|     '()V');
 | |
|   FSqliteClosable_releaseReference := javaEnvRef^^.GetMethodID(javaEnvRef, FSQLiteClosableClass, 'releaseReference',
 | |
|     '()V');
 | |
|   //
 | |
|   // Methods from FDBCursor
 | |
|   //
 | |
|   FDBCursor_getColumnCount := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getColumnCount',
 | |
|     '()I');
 | |
|   // abstract String getColumnName(int columnIndex)
 | |
|   FDBCursor_getColumnName := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getColumnName',
 | |
|     '(I)Ljava/lang/String;');
 | |
|   // public abstract int getType (int columnIndex) // Added in API level 11
 | |
|   if android_os_Build_VERSION_SDK_INT >= 11 then
 | |
|   begin
 | |
|     FDBCursor_getType := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getType',
 | |
|       '(I)I');
 | |
|   end;
 | |
|   // abstract void 	close()
 | |
|   FDBCursor_close := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'close',
 | |
|     '()V');
 | |
|   // public abstract int getCount ()
 | |
|   FDBCursor_getCount := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getCount',
 | |
|     '()I');
 | |
|   // public abstract double getDouble (int columnIndex)
 | |
|   FDBCursor_getDouble := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getDouble',
 | |
|     '(I)D');
 | |
|   // public abstract long getLong (int columnIndex)
 | |
|   FDBCursor_getLong := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getLong',
 | |
|     '(I)J');
 | |
|   // public abstract int getPosition ()
 | |
|   FDBCursor_getPosition := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getPosition',
 | |
|     '()I');
 | |
|   // public abstract String getString (int columnIndex)
 | |
|   FDBCursor_getString := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'getString',
 | |
|     '(I)Ljava/lang/String;');
 | |
|   // public abstract boolean moveToFirst ()
 | |
|   FDBCursor_moveToFirst := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'moveToFirst',
 | |
|     '()Z');
 | |
|   // public abstract boolean moveToNext ()
 | |
|   FDBCursor_moveToNext := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'moveToNext',
 | |
|     '()Z');
 | |
|   // public abstract boolean moveToPosition (int position)
 | |
|   FDBCursor_moveToPosition := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'moveToPosition',
 | |
|     '(I)Z');
 | |
|   // public abstract boolean moveToPrevious ()
 | |
|   FDBCursor_moveToPrevious := javaEnvRef^^.GetMethodID(javaEnvRef, FDBCursorClass, 'moveToPrevious',
 | |
|     '()Z');
 | |
| end;
 | |
| 
 | |
| procedure TSqliteJNIDataset.RealInternalCloseHandle;
 | |
| {var
 | |
|   inTransaction: Boolean;}
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.RealInternalCloseHandle]');
 | |
| 
 | |
|   {// Before closing the reference, wait until the last transaction has finished
 | |
|   inTransaction := javaEnvRef^^.CallBooleanMethod(javaEnvRef, AndroidDB, FSqliteDatabase_inTransaction) = JNI_TRUE;
 | |
|   while inTransaction do
 | |
|   begin
 | |
|     DebugLn('[TSqliteJNIDataset.RealInternalCloseHandle] Waiting 100ms for the transaction to close');
 | |
|     Sleep(100);
 | |
|     inTransaction := javaEnvRef^^.CallBooleanMethod(javaEnvRef, AndroidDB, FSqliteDatabase_inTransaction) = JNI_TRUE;
 | |
|   end;}
 | |
| 
 | |
|   // void android.database.sqlite.SQLiteClosable->releaseReference()
 | |
|   javaEnvRef^^.CallVoidMethod(javaEnvRef, AndroidDB, FSqliteClosable_releaseReference);
 | |
|   javaEnvRef^^.DeleteLocalRef(javaEnvRef, AndroidDB);
 | |
| 
 | |
|   //f/sqlite3_close(FSqliteHandle);
 | |
|   FSqliteHandle := nil;
 | |
|   //todo:handle return data
 | |
| end;
 | |
| 
 | |
| function TSqliteJNIDataset.RealInternalGetHandle: Pointer;
 | |
| const
 | |
|   CheckFileSql = 'Select Name from sqlite_master LIMIT 1';
 | |
| var
 | |
|   // array for the parameters
 | |
|   lParams: array[0..2] of JValue;
 | |
|   lJavaString: JString;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.RealInternalGetHandle]');
 | |
| 
 | |
|   // static SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory)
 | |
|   // preparations
 | |
|   lJavaString :=javaEnvRef^^.NewStringUTF(javaEnvRef, PChar(FFileName));
 | |
|   lParams[0].l := lJavaString;
 | |
|   lParams[1].l := nil;
 | |
|   // Call the method
 | |
|   AndroidDB := javaEnvRef^^.CallStaticObjectMethodA(javaEnvRef, FSqliteDatabaseClass, FSqliteDatabase_openOrCreateDatabase, @lParams[0]);
 | |
|   CheckJNIException(True);
 | |
|   // clean up
 | |
|   javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
 | |
| 
 | |
|   // Reinforce by getting a reference to the object, not only creating it -> Doesnt seam to make any difference
 | |
|   //javaEnvRef^^.CallVoidMethod(javaEnvRef, AndroidDB, FSqliteClosable_acquireReference);
 | |
|   Result := Pointer(AndroidDB);
 | |
|   DebugLn(Format('[TSqliteJNIDataset.RealInternalGetHandle] AndroidDB=%x', [PtrInt(AndroidDB)]));
 | |
| end;
 | |
| 
 | |
| procedure TSqliteJNIDataset.BuildLinkedList;
 | |
| var
 | |
|   TempItem: PDataRecord;
 | |
|   Counter, ColumnCount, TrueRowCount: Integer;
 | |
|   lIsAfterLastRow: Boolean;
 | |
|   //
 | |
|   lJavaString: JString;
 | |
|   lNativeString: PChar;
 | |
|   dbCursor: JObject;
 | |
|   lParams: array[0..7] of JValue;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.BuildLinkedList] FEffectiveSQL='+FEffectiveSQL);
 | |
|   RealInternalGetHandle();
 | |
| {  //Get AutoInc Field initial value
 | |
|   if FAutoIncFieldNo <> -1 then
 | |
|     sqlite3_exec(FSqliteHandle, PChar('Select Max(' + FieldDefs[FAutoIncFieldNo].Name +
 | |
|       ') from ' + FTableName), @GetAutoIncValue, @FNextAutoInc, nil);}
 | |
| 
 | |
|   //FReturnCode := sqlite3_prepare(FSqliteHandle, PChar(FEffectiveSQL), -1, @vm, nil);
 | |
|   //if FReturnCode <> SQLITE_OK then
 | |
|   //  DatabaseError(ReturnString, Self);
 | |
|   //
 | |
|   // public Cursor rawQuery (String sql, String[] selectionArgs)
 | |
|   lParams[0].l := javaEnvRef^^.NewStringUTF(javaEnvRef, PChar(FEffectiveSQL));
 | |
|   lParams[1].l := nil;
 | |
|   dbCursor := javaEnvRef^^.CallObjectMethodA(javaEnvRef, AndroidDB, FSqliteDatabase_rawQuery, @lParams[0]);
 | |
|   javaEnvRef^^.DeleteLocalRef(javaEnvRef, lParams[0].l);
 | |
| 
 | |
|   FDataAllocated := True;
 | |
| 
 | |
|   TempItem := FBeginItem;
 | |
|   FRecordCount := 0;
 | |
|   ColumnCount := javaEnvRef^^.CallIntMethod(javaEnvRef, dbCursor, FDBCursor_getColumnCount);
 | |
|   TrueRowCount := javaEnvRef^^.CallIntMethod(javaEnvRef, dbCursor, FDBCursor_getCount);
 | |
|   FRowCount := ColumnCount;
 | |
|   //add extra rows for calculated fields
 | |
|   if FCalcFieldList <> nil then
 | |
|     Inc(FRowCount, FCalcFieldList.Count);
 | |
|   FRowBufferSize := (SizeOf(PPChar) * FRowCount);
 | |
|   //FReturnCode := sqlite3_step(vm);
 | |
|   //while FReturnCode = SQLITE_ROW do
 | |
|   //begin
 | |
|   //
 | |
|   // public abstract boolean moveToNext ()
 | |
|   DebugLn(Format('[TSqliteJNIDataset.BuildLinkedList] ColCount=%d RowCount=%d', [ColumnCount, TrueRowCount]));
 | |
|   if TrueRowCount > 0 then
 | |
|   begin
 | |
|     lIsAfterLastRow := javaEnvRef^^.CallBooleanMethod(javaEnvRef, dbCursor, FDBCursor_moveToNext) = JNI_FALSE;
 | |
|     while not lIsAfterLastRow do
 | |
|     begin
 | |
|       Inc(FRecordCount);
 | |
|       DebugLn(Format('[TSqliteJNIDataset.BuildLinkedList] reading row # %d', [FRecordCount]));
 | |
|       New(TempItem^.Next);
 | |
|       TempItem^.Next^.Previous := TempItem;
 | |
|       TempItem := TempItem^.Next;
 | |
|       GetMem(TempItem^.Row, FRowBufferSize);
 | |
|       for Counter := 0 to ColumnCount - 1 do
 | |
|       begin
 | |
|         DebugLn(Format('[TSqliteJNIDataset.BuildLinkedList] reading row # %d col # %d', [FRecordCount, Counter]));
 | |
|         // TempItem^.Row[Counter] := StrNew(sqlite3_column_text(vm, Counter));
 | |
|         // public abstract String getString (int columnIndex)
 | |
|         lParams[0].i := Counter;
 | |
|         lJavaString := javaEnvRef^^.CallObjectMethodA(javaEnvRef, dbCursor, FDBCursor_getString, @lParams[0]);
 | |
|         //DebugLn(Format('[TSqliteJNIDataset.BuildLinkedList] lJavaString=%x', [PtrInt(lJavaString)]));
 | |
|         if lJavaString <> nil then
 | |
|         begin
 | |
|           lNativeString := javaEnvRef^^.GetStringUTFChars(javaEnvRef, lJavaString, nil);
 | |
|           TempItem^.Row[Counter] := StrNew(lNativeString);
 | |
|           javaEnvRef^^.ReleaseStringUTFChars(javaEnvRef, lJavaString, lNativeString);
 | |
|           javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
 | |
|         end
 | |
|         else
 | |
|           TempItem^.Row[Counter] := StrNew('');
 | |
|       end;
 | |
|       //initialize calculated fields with nil
 | |
|       for Counter := ColumnCount to FRowCount - 1 do
 | |
|         TempItem^.Row[Counter] := nil;
 | |
|       //FReturnCode := sqlite3_step(vm);
 | |
|       lIsAfterLastRow := javaEnvRef^^.CallBooleanMethod(javaEnvRef, dbCursor, FDBCursor_moveToNext) = JNI_FALSE;
 | |
|     end;
 | |
|   end;
 | |
|   //sqlite3_finalize(vm);
 | |
|   javaEnvRef^^.CallVoidMethod(javaEnvRef, dbCursor, FDBCursor_close);
 | |
| 
 | |
|   // Attach EndItem
 | |
|   TempItem^.Next := FEndItem;
 | |
|   FEndItem^.Previous := TempItem;
 | |
| 
 | |
|   // Alloc temporary item used in append/insert
 | |
|   GetMem(FCacheItem^.Row, FRowBufferSize);
 | |
|   for Counter := 0 to FRowCount - 1 do
 | |
|     FCacheItem^.Row[Counter] := nil;
 | |
|   // Fill FBeginItem.Row with nil -> necessary for avoid exceptions in empty datasets
 | |
|   GetMem(FBeginItem^.Row, FRowBufferSize);
 | |
|   //Todo: see if is better to nullif using FillDWord
 | |
|   for Counter := 0 to FRowCount - 1 do
 | |
|     FBeginItem^.Row[Counter] := nil;
 | |
| 
 | |
|   RealInternalCloseHandle();
 | |
| end;
 | |
| 
 | |
| function TSqliteJNIDataset.GetLastInsertRowId: Int64;
 | |
| begin
 | |
|   Result := FLastInsertRowId;
 | |
|   DebugLn('[TSqliteJNIDataset.GetLastInsertRowId] Result='+IntToStr(Result));
 | |
|   //f/Result := sqlite3_last_insert_rowid(FSqliteHandle);
 | |
| end;
 | |
| 
 | |
| function TSqliteJNIDataset.GetRowsAffected: Integer;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.GetRowsAffected]');
 | |
|   //f/Result := sqlite3_changes(FSqliteHandle);
 | |
| end;
 | |
| 
 | |
| procedure TSqliteJNIDataset.InternalCloseHandle;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.InternalCloseHandle]');
 | |
|   // RealInternalCloseHandle();
 | |
| end;
 | |
| 
 | |
| 
 | |
| function TSqliteJNIDataset.InternalGetHandle: Pointer;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.InternalGetHandle]');
 | |
|   FindJavaClassesAndMethods();
 | |
|   Result := Pointer($beef);
 | |
| end;
 | |
| 
 | |
| procedure TSqliteJNIDataset.RetrieveFieldDefs;
 | |
| var
 | |
|   vm: Pointer;
 | |
|   ColumnName: string;
 | |
|   i, ColumnCount, RowCount, DataSize: Integer;
 | |
|   AType: TFieldType;
 | |
|   //
 | |
|   lColumnType: JInt;
 | |
|   lJavaString: JString;
 | |
|   lNativeString: PChar;
 | |
|   dbCursor: JObject;
 | |
|   lParams: array[0..7] of JValue;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs]');
 | |
|   RealInternalGetHandle();
 | |
|   FAutoIncFieldNo := -1;
 | |
|   FieldDefs.Clear;
 | |
| 
 | |
|   //FReturnCode := sqlite3_prepare(FSqliteHandle, PChar(FEffectiveSQL), -1, @vm, nil);
 | |
|   //if FReturnCode <> SQLITE_OK then
 | |
|   //  DatabaseError(ReturnString, Self);
 | |
|   //
 | |
|   // Cursor c = db.query(tableName, null, null, null, null, null, null);
 | |
|   // public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)
 | |
|   lParams[0].l :=javaEnvRef^^.NewStringUTF(javaEnvRef, PChar(TableName));
 | |
|   lParams[1].l := nil;
 | |
|   lParams[2].l := nil;
 | |
|   lParams[3].l := nil;
 | |
|   lParams[4].l := nil;
 | |
|   lParams[5].l := nil;
 | |
|   lParams[6].l := nil;
 | |
|   lParams[7].l := nil;
 | |
|   dbCursor := javaEnvRef^^.CallObjectMethodA(javaEnvRef, AndroidDB, FSqliteDatabase_query, @lParams[0]);
 | |
| 
 | |
|   if dbCursor = nil then
 | |
|   begin
 | |
|     DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs] dbCursor = nil');
 | |
|     Exit;
 | |
|   end;
 | |
|   if CheckJNIException(False) then
 | |
|   begin
 | |
|     DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs] Java Exceptiong calling AndroidDB.query ' + ReturnString);
 | |
|     DatabaseError(ReturnString, Self);
 | |
|     Exit;
 | |
|   end;
 | |
| 
 | |
|   //sqlite3_step(vm);
 | |
| 
 | |
|   //
 | |
|   // Obtain the number of columns
 | |
|   //
 | |
| 
 | |
|   // abstract String getColumnName(int columnIndex)
 | |
|   //    int ColumnCount = c.getColumnCount();
 | |
|   ColumnCount := javaEnvRef^^.CallIntMethod(javaEnvRef, dbCursor, FDBCursor_getColumnCount);
 | |
|   RowCount := javaEnvRef^^.CallIntMethod(javaEnvRef, dbCursor, FDBCursor_getCount);
 | |
|   //DebugLn(Format('[TSqliteJNIDataset.RetrieveFieldDefs] ColumnCount=%d RowCount=%d', [ColumnCount, RowCount]));
 | |
|   //Prepare the array of pchar2sql functions
 | |
|   SetLength(FGetSqlStr, ColumnCount);
 | |
|   for i := 0 to ColumnCount - 1 do
 | |
|   begin
 | |
|     //DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs] for i='+IntToStr(i));
 | |
|     //
 | |
|     // First get the column name
 | |
|     //
 | |
|     // abstract String getColumnName(int columnIndex)
 | |
|     lParams[0].i := i;
 | |
|     lJavaString := javaEnvRef^^.CallObjectMethodA(javaEnvRef, dbCursor, FDBCursor_getColumnName, @lParams[0]);
 | |
|     lNativeString := javaEnvRef^^.GetStringUTFChars(javaEnvRef, lJavaString, nil);
 | |
|     ColumnName := lNativeString;
 | |
|     javaEnvRef^^.ReleaseStringUTFChars(javaEnvRef, lJavaString, lNativeString);
 | |
|     javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
 | |
|     //DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs] ColumnName='+ColumnName);
 | |
| 
 | |
|     //
 | |
|     // Now obtain the data size and type
 | |
|     //
 | |
|     DataSize := 0;
 | |
| 
 | |
|     // Before Android 3.0 there is no way to know the type of the field, so just suppose it is string
 | |
|     if (android_os_Build_VERSION_SDK_INT < 11) or (RowCount <= 0) then
 | |
|     begin
 | |
|       AType := ftString;
 | |
|       DataSize := DefaultStringSize;
 | |
|     end
 | |
|     else
 | |
|     // In Android 3.0 we can use Cursor.getType
 | |
|     // but it throws a CursorIndexOutOfBoundsException if the cursor is in position -1
 | |
|     // so if the database has no rows, getType doesn't work o.O
 | |
|     begin
 | |
|       // getType won't work if we don't first move to a row
 | |
|       javaEnvRef^^.CallBooleanMethod(javaEnvRef, dbCursor, FDBCursor_moveToFirst);
 | |
| 
 | |
|       // public abstract int getType (int columnIndex) // Added in API level 11
 | |
|       lParams[0].i := i;
 | |
|       lColumnType := javaEnvRef^^.CallIntMethodA(javaEnvRef, dbCursor, FDBCursor_getType, @lParams[0]);
 | |
|       //DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs] dbCursor.getType()='+IntToStr(lColumnType));
 | |
| 
 | |
|       case lColumnType of
 | |
|         FIELD_TYPE_BLOB:
 | |
|         begin
 | |
|           AType := ftString;
 | |
|           DataSize := DefaultStringSize;
 | |
|         end;
 | |
|         FIELD_TYPE_FLOAT:
 | |
|         begin
 | |
|           AType := ftFloat;
 | |
|         end;
 | |
|         FIELD_TYPE_INTEGER:
 | |
|         begin
 | |
|           {if AutoIncrementKey and (UpperCase(String(sqlite3_column_name(vm, i))) = UpperCase(PrimaryKey)) then
 | |
|           begin
 | |
|             AType := ftAutoInc;
 | |
|             FAutoIncFieldNo := i;
 | |
|           end
 | |
|           else}
 | |
|             AType := ftInteger;
 | |
|         end;
 | |
|         FIELD_TYPE_NULL:
 | |
|         begin
 | |
|           AType := ftString;
 | |
|           DataSize := DefaultStringSize;
 | |
|         end;
 | |
|         FIELD_TYPE_STRING:
 | |
|         begin
 | |
|           AType := ftString;
 | |
|           DataSize := DefaultStringSize;
 | |
|         end;
 | |
|       end;
 | |
|     end;
 | |
| 
 | |
|     FieldDefs.Add(ColumnName, AType, DataSize);
 | |
|     //Set the pchar2sql function
 | |
|     if AType in [ftString, ftMemo] then
 | |
|       FGetSqlStr[i] := @Char2SQLStr
 | |
|     else
 | |
|       FGetSqlStr[i] := @Num2SQLStr;
 | |
|     {$ifdef DEBUG_SQLITEDS}
 | |
|     DebugLn('  Field[', i, '] Name: ', sqlite3_column_name(vm, i));
 | |
|     DebugLn('  Field[', i, '] Type: ', sqlite3_column_decltype(vm, i));
 | |
|     {$endif}
 | |
|   end;
 | |
|   //sqlite3_finalize(vm);
 | |
|   javaEnvRef^^.CallVoidMethod(javaEnvRef, dbCursor, FDBCursor_close);
 | |
|   RealInternalCloseHandle();
 | |
| end;
 | |
| 
 | |
| function TSqliteJNIDataset.SqliteExec(ASQL: PChar; ACallback: TSqliteCdeclCallback; Data: Pointer): Integer;
 | |
| var
 | |
|   // array for the parameters
 | |
|   lParams: array[0..2] of JValue;
 | |
|   lJavaString: JString;
 | |
|   lSQLStrings: TStrings;
 | |
|   i: Integer;
 | |
| begin
 | |
|   DebugLn(Format('[TSqliteJNIDataset.SqliteExec] AndroidDB=%x ACallback=%x Data=%x ASQL=%s',
 | |
|     [PtrInt(AndroidDB), PtrInt(ACallback), PtrInt(Data), StrPas(ASQL)]));
 | |
| 
 | |
|   // If we don't renew our AndroidDB, we get crashes =/
 | |
|   //I/lclapp  (  901): [TSqliteJNIDataset.SqliteExec] AndroidDB=410A5178 ACallback=0 Data=0 ASQL=BEGIN;INSERT INTO TestTable (FirstFieldStr,SecondFieldInt) VALUES ('fe1
 | |
|   //I/lclapp  (  901): ',NULL);COMMIT;
 | |
|   // W/dalvikvm(  901): JNI WARNING: 0x410a5178 is not a valid JNI reference
 | |
|   // W/dalvikvm(  901):              in Lcom/pascal/lcltest/LCLActivity;.LCLOnTouch:(FFI)I (CallVoidMethodA)
 | |
|   RealInternalGetHandle();
 | |
| 
 | |
|   // Split the SQL and execute each part
 | |
|   // because SqliteDatabase.execSQL can execute only 1 single SQL statement, not multiple ones
 | |
|   lSQLStrings := SplitSQLStatements(StrPas(ASQL));
 | |
| 
 | |
|   for i := 0 to lSQLStrings.Count-1 do
 | |
|   begin
 | |
|     DebugLn('[TSqliteJNIDataset.SqliteExec] Executing SQL part: ' + lSQLStrings.Strings[i]);
 | |
|     // void execSQL(String sql)
 | |
|     // preparations
 | |
|     lJavaString := javaEnvRef^^.NewStringUTF(javaEnvRef, PChar(lSQLStrings.Strings[i]));
 | |
|     lParams[0].l := lJavaString;
 | |
|     // Call the method
 | |
|     javaEnvRef^^.CallVoidMethodA(javaEnvRef, AndroidDB, FSqliteDatabase_execSQL, @lParams[0]);
 | |
|     // clean up
 | |
|     javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
 | |
|   end;
 | |
| 
 | |
|   // Call the callback
 | |
|   if ACallback <> nil then
 | |
|   begin
 | |
|     DebugLn('[TSqliteJNIDataset.SqliteExec] Calling callback');
 | |
|     ACallback(Data, 0, nil, nil);
 | |
|   end;
 | |
| 
 | |
|   RealInternalCloseHandle();
 | |
|   lSQLStrings.Free;
 | |
| 
 | |
|   DebugLn('[TSqliteJNIDataset.SqliteExec] END');
 | |
| end;
 | |
| 
 | |
| function TSqliteJNIDataset.CheckJNIException(ARaisePascalException: Boolean): Boolean;
 | |
| var
 | |
|   exceptionObj: jthrowable;
 | |
|   javaLangClass: jclass;
 | |
|   javaLangClass_getName: JMethodID;
 | |
|   lJavaString: JString;
 | |
|   lNativeString: PChar;
 | |
| begin
 | |
|   Result := False;
 | |
|   FReturnString := '';
 | |
|   // There seams to be no way to get any information about the exception in JNI =(
 | |
|   //DebugLn('[TSqliteJNIDataset.CheckJNIException] START');
 | |
| 
 | |
|   if javaEnvRef^^.ExceptionCheck(javaEnvRef) = JNI_FALSE then
 | |
|   begin
 | |
|     Exit;
 | |
|   end;
 | |
| 
 | |
|   DebugLn('[TSqliteJNIDataset.CheckJNIException] Exception found');
 | |
|   Result := True;
 | |
|   FReturnString := 'JNI Exception! See adb logcat for more details.';
 | |
|   exceptionObj := javaEnvRef^^.ExceptionOccurred(javaEnvRef);
 | |
|   javaEnvRef^^.ExceptionDescribe(javaEnvRef);
 | |
|   javaEnvRef^^.ExceptionClear(javaEnvRef);
 | |
|   if ARaisePascalException then raise Exception.Create(FReturnString);
 | |
| 
 | |
|   // Code for reading info from the exception object has failed for me.
 | |
|   {DebugLn('[TSqliteJNIDataset.PrepareReturnString] A exceptionObj='+IntToHex(Cardinal(exceptionObj), 8));
 | |
|   javaLangClass := javaEnvRef^^.FindClass(javaEnvRef, 'java/lang/Class');
 | |
|   javaLangClass_getName := javaEnvRef^^.GetMethodID(javaEnvRef, javaLangClass, 'getName', '()Ljava/lang/String;');
 | |
|   DebugLn('[TSqliteJNIDataset.PrepareReturnString] B');
 | |
|   lJavaString := javaEnvRef^^.CallObjectMethod(javaEnvRef, exceptionObj, javaLangClass_getName); // <--- crashes here
 | |
|   DebugLn('[TSqliteJNIDataset.PrepareReturnString] C lJavaString='+IntToHex(Cardinal(lJavaString), 8));
 | |
|   lNativeString := javaEnvRef^^.GetStringUTFChars(javaEnvRef, lJavaString, nil);
 | |
|   DebugLn('[TSqliteJNIDataset.PrepareReturnString] D');
 | |
|   FReturnString := StrPas(lNativeString);
 | |
|   javaEnvRef^^.ReleaseStringUTFChars(javaEnvRef, lJavaString, lNativeString);
 | |
|   javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
 | |
|   javaEnvRef^^.DeleteLocalRef(javaEnvRef, exceptionObj);
 | |
|   DebugLn('[TSqliteJNIDataset.PrepareReturnString] FReturnString=' + FReturnString);}
 | |
| end;
 | |
| 
 | |
| procedure TSqliteJNIDataset.ExecuteDirect(const ASQL: String);
 | |
| var
 | |
|   // array for the parameters
 | |
|   lParams: array[0..2] of JValue;
 | |
|   lJavaString: JString;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.ExecuteDirect] ' + ASQL);
 | |
|   RealInternalGetHandle();
 | |
| 
 | |
|   // void execSQL(String sql)
 | |
|   // preparations
 | |
|   lJavaString :=javaEnvRef^^.NewStringUTF(javaEnvRef, PChar(ASQL));
 | |
|   lParams[0].l := lJavaString;
 | |
|   // Call the method
 | |
|   javaEnvRef^^.CallVoidMethodA(javaEnvRef, AndroidDB, FSqliteDatabase_execSQL, @lParams[0]);
 | |
|   // clean up
 | |
|   javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
 | |
|   // Check for exceptions
 | |
|   CheckJNIException(True);
 | |
| 
 | |
|   RealInternalCloseHandle();
 | |
| 
 | |
|   {FReturnCode := sqlite3_prepare(FSqliteHandle, Pchar(ASQL), -1, @vm, nil);
 | |
|   if FReturnCode <> SQLITE_OK then
 | |
|     DatabaseError(ReturnString, Self);
 | |
|   FReturnCode := sqlite3_step(vm);
 | |
|   sqlite3_finalize(vm);}
 | |
| end;
 | |
| 
 | |
| function TSqliteJNIDataset.QuickQuery(const ASQL: String; const AStrList: TStrings; FillObjects:Boolean): String;
 | |
| var
 | |
|   vm: Pointer;
 | |
|     
 | |
| {  procedure FillStrings;
 | |
|   begin
 | |
|     while FReturnCode = SQLITE_ROW do
 | |
|     begin
 | |
|       AStrList.Add(String(sqlite3_column_text(vm,0)));
 | |
|       FReturnCode := sqlite3_step(vm);
 | |
|     end;
 | |
|   end;
 | |
|   procedure FillStringsAndObjects;
 | |
|   begin
 | |
|     while FReturnCode = SQLITE_ROW do
 | |
|     begin
 | |
|       AStrList.AddObject(String(sqlite3_column_text(vm, 0)),
 | |
|         TObject(PtrInt(sqlite3_column_int(vm, 1))));
 | |
|       FReturnCode := sqlite3_step(vm);
 | |
|     end;
 | |
|   end;    }
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.QuickQuery]');
 | |
| {  if FSqliteHandle = nil then
 | |
|     GetSqliteHandle;
 | |
|   Result := '';
 | |
|   FReturnCode := sqlite3_prepare(FSqliteHandle,Pchar(ASQL), -1, @vm, nil);
 | |
|   if FReturnCode <> SQLITE_OK then
 | |
|     DatabaseError(ReturnString, Self);
 | |
|     
 | |
|   FReturnCode := sqlite3_step(vm);
 | |
|   if (FReturnCode = SQLITE_ROW) and (sqlite3_column_count(vm) > 0) then
 | |
|   begin
 | |
|     Result := String(sqlite3_column_text(vm, 0));
 | |
|     if AStrList <> nil then
 | |
|     begin   
 | |
|       if FillObjects and (sqlite3_column_count(vm) > 1) then
 | |
|         FillStringsAndObjects
 | |
|       else
 | |
|         FillStrings;
 | |
|     end;          
 | |
|   end;  
 | |
|   sqlite3_finalize(vm); }
 | |
| end;
 | |
| 
 | |
| function TSqliteJNIDataset.ReturnString: String;
 | |
| begin
 | |
|   Result := FReturnString;
 | |
| end;
 | |
| 
 | |
| class function TSqliteJNIDataset.SqliteVersion: String;
 | |
| var
 | |
|   intVersion: JInt;
 | |
| begin
 | |
|   DebugLn('[TSqliteJNIDataset.SqliteVersion]');
 | |
|   // public int getVersion ()
 | |
|   //intVersion := javaEnvRef^^.CallIntMethod(javaEnvRef, AndroidDB, FSqliteClosable_getVersion);
 | |
|   Result := '3.4'; // IntToStr(intVersion); cant access AndroidDB in class method
 | |
|   DebugLn('[TSqliteJNIDataset.SqliteVersion] Result='+Result);
 | |
| end;
 | |
| 
 | |
| end.
 | |
| 
 | 
