LCL-Android-Sqlite: Changes the architecture to recreate the handle on each routine because it was crashing. Reorders the methods to be according to the interface. Fixes many issues and crashes and bugs in the Android-Sqlite dataset code.

git-svn-id: trunk@39503 -
This commit is contained in:
sekelsenmat 2012-12-10 17:37:41 +00:00
parent bc13aee2de
commit 43740984e2
3 changed files with 404 additions and 281 deletions

View File

@ -10,7 +10,7 @@ object formSqlite: TformSqlite
object btnConnect: TButton object btnConnect: TButton
Left = 8 Left = 8
Height = 25 Height = 25
Top = 24 Top = 40
Width = 112 Width = 112
Caption = 'Connect' Caption = 'Connect'
OnClick = btnConnectClick OnClick = btnConnectClick
@ -19,7 +19,7 @@ object formSqlite: TformSqlite
object DBEdit1: TDBEdit object DBEdit1: TDBEdit
Left = 8 Left = 8
Height = 23 Height = 23
Top = 88 Top = 104
Width = 80 Width = 80
DataField = 'FirstFieldStr' DataField = 'FirstFieldStr'
DataSource = SqliteDatasource DataSource = SqliteDatasource
@ -30,7 +30,7 @@ object formSqlite: TformSqlite
object DBNavigator1: TDBNavigator object DBNavigator1: TDBNavigator
Left = 8 Left = 8
Height = 25 Height = 25
Top = 56 Top = 72
Width = 241 Width = 241
BevelOuter = bvNone BevelOuter = bvNone
ChildSizing.EnlargeHorizontal = crsScaleChilds ChildSizing.EnlargeHorizontal = crsScaleChilds
@ -48,7 +48,7 @@ object formSqlite: TformSqlite
object btnCreateDB: TButton object btnCreateDB: TButton
Left = 136 Left = 136
Height = 25 Height = 25
Top = 24 Top = 8
Width = 113 Width = 113
Caption = 'Create DB' Caption = 'Create DB'
OnClick = btnCreateDBClick OnClick = btnCreateDBClick
@ -57,14 +57,23 @@ object formSqlite: TformSqlite
object Edit1: TEdit object Edit1: TEdit
Left = 8 Left = 8
Height = 23 Height = 23
Top = 136 Top = 152
Width = 80 Width = 80
ReadOnly = True ReadOnly = True
TabOrder = 4 TabOrder = 4
Text = 'Edit1' Text = 'Edit1'
end end
object btnSaveDB: TButton
Left = 137
Height = 25
Top = 40
Width = 112
Caption = 'Save DB'
OnClick = btnSaveDBClick
TabOrder = 5
end
object SqliteDatasource: TDatasource object SqliteDatasource: TDatasource
left = 136 left = 136
top = 88 top = 104
end end
end end

View File

@ -6,7 +6,13 @@ interface
uses uses
Classes, SysUtils, db, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Classes, SysUtils, db, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
DbCtrls; DbCtrls
{$ifdef CPUARM}
,sqlitejniandroid
{$else}
,sqlite3ds
{$endif}
;
type type
@ -14,6 +20,7 @@ type
TformSqlite = class(TForm) TformSqlite = class(TForm)
btnConnect: TButton; btnConnect: TButton;
btnSaveDB: TButton;
btnCreateDB: TButton; btnCreateDB: TButton;
Edit1: TEdit; Edit1: TEdit;
SqliteDatasource: TDatasource; SqliteDatasource: TDatasource;
@ -21,10 +28,12 @@ type
DBNavigator1: TDBNavigator; DBNavigator1: TDBNavigator;
procedure btnCreateDBClick(Sender: TObject); procedure btnCreateDBClick(Sender: TObject);
procedure btnConnectClick(Sender: TObject); procedure btnConnectClick(Sender: TObject);
procedure btnSaveDBClick(Sender: TObject);
private private
{ private declarations } { private declarations }
public public
{ public declarations } { public declarations }
sqlitedb: {$ifdef CPUARM}TSqliteJNIDataset;{$else}TSqlite3Dataset;{$endif}
end; end;
var var
@ -32,19 +41,11 @@ var
implementation implementation
{$ifdef CPUARM}
uses sqlitejniandroid;
{$else}
uses sqlite3ds;
{$endif}
{$R *.lfm} {$R *.lfm}
{ TformSqlite } { TformSqlite }
procedure TformSqlite.btnConnectClick(Sender: TObject); procedure TformSqlite.btnConnectClick(Sender: TObject);
var
sqlitedb: {$ifdef CPUARM}TSqliteJNIDataset;{$else}TSqlite3Dataset;{$endif}
begin begin
{$ifdef CPUARM} {$ifdef CPUARM}
sqlitedb := TSqliteJNIDataset.Create(Self); sqlitedb := TSqliteJNIDataset.Create(Self);
@ -61,23 +62,30 @@ begin
SqliteDatasource.DataSet := sqlitedb; SqliteDatasource.DataSet := sqlitedb;
end; end;
procedure TformSqlite.btnSaveDBClick(Sender: TObject);
begin
if sqlitedb = nil then Exit;
sqlitedb.Close();
sqlitedb.Open();
end;
procedure TformSqlite.btnCreateDBClick(Sender: TObject); procedure TformSqlite.btnCreateDBClick(Sender: TObject);
var var
sqlitedb: {$ifdef CPUARM}TSqliteJNIDataset;{$else}TSqlite3Dataset;{$endif} lsqlitedb: {$ifdef CPUARM}TSqliteJNIDataset;{$else}TSqlite3Dataset;{$endif}
begin begin
{$ifdef CPUARM} {$ifdef CPUARM}
sqlitedb := TSqliteJNIDataset.Create(Self); lsqlitedb := TSqliteJNIDataset.Create(Self);
sqlitedb.FileName := '/sdcard/database.db'; lsqlitedb.FileName := '/sdcard/database.db';
{$else} {$else}
sqlitedb := TSqlite3Dataset.Create(Self); lsqlitedb := TSqlite3Dataset.Create(Self);
sqlitedb.FileName := 'database.db'; lsqlitedb.FileName := 'database.db';
{$endif} {$endif}
//SqliteDatasource.DataSet := sqlitedb; //SqliteDatasource.DataSet := sqlitedb;
sqlitedb.TableName := 'TestTable'; lsqlitedb.TableName := 'TestTable';
sqlitedb.FieldDefs.Add('FirstFieldStr', ftString); lsqlitedb.FieldDefs.Add('FirstFieldStr', ftString);
sqlitedb.FieldDefs.Add('SecondFieldInt', ftInteger); lsqlitedb.FieldDefs.Add('SecondFieldInt', ftInteger);
sqlitedb.CreateTable(); lsqlitedb.CreateTable();
sqlitedb.Free; lsqlitedb.Free;
end; end;
end. end.

View File

@ -51,10 +51,11 @@ type
// Java Classes // Java Classes
FSqliteClosableClass, FSQLiteDatabaseClass, FDBCursorClass: JClass; FSqliteClosableClass, FSQLiteDatabaseClass, FDBCursorClass: JClass;
// Java Methods // Java Methods
FSqliteClosable_releaseReference: JMethodID; FSqliteClosable_acquireReference, FSqliteClosable_releaseReference: JMethodID;
FSqliteDatabase_ExecSQLMethod, FSqliteDatabase_openOrCreateDatabase, FSqliteDatabase_ExecSQLMethod, FSqliteDatabase_openOrCreateDatabase,
FSqliteDatabase_getVersion, FSqliteDatabase_query, FSqliteDatabase_getVersion, FSqliteDatabase_query,
FSqliteDatabase_execSQL, FSqliteDatabase_rawQuery: JMethodID; FSqliteDatabase_execSQL, FSqliteDatabase_rawQuery,
FSqliteDatabase_inTransaction: JMethodID;
FDBCursor_getColumnCount, FDBCursor_getColumnName, FDBCursor_getType, FDBCursor_getColumnCount, FDBCursor_getColumnName, FDBCursor_getType,
FDBCursor_close, FDBCursor_getCount, FDBCursor_getDouble, FDBCursor_close, FDBCursor_getCount, FDBCursor_getDouble,
FDBCursor_getLong, FDBCursor_getPosition, FDBCursor_getString, FDBCursor_getLong, FDBCursor_getPosition, FDBCursor_getString,
@ -62,20 +63,23 @@ type
FDBCursor_moveToPrevious: JMethodID; FDBCursor_moveToPrevious: JMethodID;
// Java Objects // Java Objects
AndroidDB: jobject; // SQLiteDatabase AndroidDB: jobject; // SQLiteDatabase
function SplitSQLStatements(AInput: string): TStrings;
procedure FindJavaClassesAndMethods; procedure FindJavaClassesAndMethods;
procedure RealInternalCloseHandle;
function RealInternalGetHandle: Pointer;
protected protected
procedure BuildLinkedList; override; procedure BuildLinkedList; override;
function GetLastInsertRowId: Int64; override; function GetLastInsertRowId: Int64; override;
function GetRowsAffected:Integer; override; function GetRowsAffected:Integer; override;
procedure InternalCloseHandle; override; procedure InternalCloseHandle; override;
function InternalGetHandle: Pointer; override; function InternalGetHandle: Pointer; override;
procedure RetrieveFieldDefs; override; procedure RetrieveFieldDefs; override;
function SqliteExec(ASQL: PChar; ACallback: TSqliteCdeclCallback; Data: Pointer): Integer; override; function SqliteExec(ASQL: PChar; ACallback: TSqliteCdeclCallback; Data: Pointer): Integer; override;
procedure PrepareReturnString; procedure PrepareReturnString;
public public
procedure ExecuteDirect(const ASQL: String); override; procedure ExecuteDirect(const ASQL: String); override;
function QuickQuery(const ASQL: String; const AStrList: TStrings; FillObjects: Boolean): String; override; function QuickQuery(const ASQL: String; const AStrList: TStrings; FillObjects: Boolean): String; override;
function ReturnString: String; override; function ReturnString: String; override;
class function SqliteVersion: String; override; class function SqliteVersion: String; override;
end; end;
@ -160,76 +164,139 @@ end;
{ TSqlite3Dataset } { TSqlite3Dataset }
function TSqliteJNIDataset.SqliteExec(ASQL: PChar; ACallback: TSqliteCdeclCallback; Data: Pointer): Integer; function TSqliteJNIDataset.SplitSQLStatements(AInput: string): TStrings;
var var
// array for the parameters i: Integer;
lParams: array[0..2] of JValue; CurChar: Char;
lJavaString: JString; CurStr: string;
InQuote: Boolean = false;
begin begin
DebugLn('[TSqliteJNIDataset.SqliteExec] ' + StrPas(ASQL)); Result := TStringList.Create;
for i := 1 to Length(AInput) do
// void execSQL(String sql)
// preparations
lJavaString :=javaEnvRef^^.NewStringUTF(javaEnvRef, ASQL);
lParams[0].l := lJavaString;
// Call the method
javaEnvRef^^.CallVoidMethodA(javaEnvRef, AndroidDB, FSqliteDatabase_execSQL, @lParams[0]);
// clean up
javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
end;
procedure TSqliteJNIDataset.PrepareReturnString;
var
exceptionObj: jthrowable;
javaLangClass: jclass;
javaLangClass_getName: JMethodID;
lJavaString: JString;
lNativeString: PChar;
begin
FReturnString := '';
// There seams to be no way to get any information about the exception in JNI =(
//DebugLn('[TSqliteJNIDataset.PrepareReturnString] START');
if javaEnvRef^^.ExceptionCheck(javaEnvRef) = JNI_FALSE then
begin begin
DebugLn('[TSqliteJNIDataset.PrepareReturnString] No exception found'); CurChar := AInput[i];
Exit;
end;
exceptionObj := javaEnvRef^^.ExceptionOccurred(javaEnvRef); if CurChar = '''' then InQuote := not InQuote;
javaEnvRef^^.ExceptionDescribe(javaEnvRef);
javaEnvRef^^.ExceptionClear(javaEnvRef); // Add a statement when it finishes with a ; outside quotes
{DebugLn('[TSqliteJNIDataset.PrepareReturnString] A exceptionObj='+IntToHex(Cardinal(exceptionObj), 8)); if (CurChar = ';') and (not InQuote) then
javaLangClass := javaEnvRef^^.FindClass(javaEnvRef, 'java/lang/Class'); begin
javaLangClass_getName := javaEnvRef^^.GetMethodID(javaEnvRef, javaLangClass, 'getName', '()Ljava/lang/String;'); Result.Add(CurStr);
DebugLn('[TSqliteJNIDataset.PrepareReturnString] B'); CurStr := '';
lJavaString := javaEnvRef^^.CallObjectMethod(javaEnvRef, exceptionObj, javaLangClass_getName); // <--- crashes here end
DebugLn('[TSqliteJNIDataset.PrepareReturnString] C lJavaString='+IntToHex(Cardinal(lJavaString), 8)); else
lNativeString := javaEnvRef^^.GetStringUTFChars(javaEnvRef, lJavaString, nil); CurStr := CurStr + CurChar;
DebugLn('[TSqliteJNIDataset.PrepareReturnString] D'); end;
FReturnString := StrPas(lNativeString); // add the last statement even if it doesn't end with a ;
javaEnvRef^^.ReleaseStringUTFChars(javaEnvRef, lJavaString, lNativeString); if CurStr <> '' then Result.Add(CurStr);
javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
javaEnvRef^^.DeleteLocalRef(javaEnvRef, exceptionObj);
DebugLn('[TSqliteJNIDataset.PrepareReturnString] FReturnString=' + FReturnString);}
end; end;
procedure TSqliteJNIDataset.InternalCloseHandle; procedure TSqliteJNIDataset.FindJavaClassesAndMethods;
begin begin
DebugLn('[TSqliteJNIDataset.InternalCloseHandle]'); 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() // void android.database.sqlite.SQLiteClosable->releaseReference()
javaEnvRef^^.CallVoidMethod(javaEnvRef, AndroidDB, FSqliteClosable_releaseReference); javaEnvRef^^.CallVoidMethod(javaEnvRef, AndroidDB, FSqliteClosable_releaseReference);
//javaEnvRef^^.DeleteLocalRef(javaEnvRef, AndroidDB); javaEnvRef^^.DeleteLocalRef(javaEnvRef, AndroidDB);
//f/sqlite3_close(FSqliteHandle); //f/sqlite3_close(FSqliteHandle);
FSqliteHandle := nil; FSqliteHandle := nil;
//todo:handle return data //todo:handle return data
end; end;
function TSqliteJNIDataset.RealInternalGetHandle: Pointer;
function TSqliteJNIDataset.InternalGetHandle: Pointer;
const const
CheckFileSql = 'Select Name from sqlite_master LIMIT 1'; CheckFileSql = 'Select Name from sqlite_master LIMIT 1';
var var
@ -237,8 +304,7 @@ var
lParams: array[0..2] of JValue; lParams: array[0..2] of JValue;
lJavaString: JString; lJavaString: JString;
begin begin
DebugLn('[TSqliteJNIDataset.InternalGetHandle]'); DebugLn('[TSqliteJNIDataset.RealInternalGetHandle]');
FindJavaClassesAndMethods();
// static SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory) // static SQLiteDatabase openOrCreateDatabase(String path, SQLiteDatabase.CursorFactory factory)
// preparations // preparations
@ -249,6 +315,133 @@ begin
AndroidDB := javaEnvRef^^.CallStaticObjectMethodA(javaEnvRef, FSqliteDatabaseClass, FSqliteDatabase_openOrCreateDatabase, @lParams[0]); AndroidDB := javaEnvRef^^.CallStaticObjectMethodA(javaEnvRef, FSqliteDatabaseClass, FSqliteDatabase_openOrCreateDatabase, @lParams[0]);
// clean up // clean up
javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString); 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
// 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] reading row # %d', [FRecordCount]));
lNativeString := javaEnvRef^^.GetStringUTFChars(javaEnvRef, lJavaString, nil);
TempItem^.Row[Counter] := StrNew(lNativeString);
javaEnvRef^^.ReleaseStringUTFChars(javaEnvRef, lJavaString, lNativeString);
javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
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; end;
procedure TSqliteJNIDataset.RetrieveFieldDefs; procedure TSqliteJNIDataset.RetrieveFieldDefs;
@ -265,6 +458,7 @@ var
lParams: array[0..7] of JValue; lParams: array[0..7] of JValue;
begin begin
DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs]'); DebugLn('[TSqliteJNIDataset.RetrieveFieldDefs]');
RealInternalGetHandle();
FAutoIncFieldNo := -1; FAutoIncFieldNo := -1;
FieldDefs.Clear; FieldDefs.Clear;
@ -341,6 +535,9 @@ begin
// but it throws a CursorIndexOutOfBoundsException if the cursor is in position -1 // 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 // so if the database has no rows, getType doesn't work o.O
begin 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 // public abstract int getType (int columnIndex) // Added in API level 11
lParams[0].i := i; lParams[0].i := i;
lColumnType := javaEnvRef^^.CallIntMethodA(javaEnvRef, dbCursor, FDBCursor_getType, @lParams[0]); lColumnType := javaEnvRef^^.CallIntMethodA(javaEnvRef, dbCursor, FDBCursor_getType, @lParams[0]);
@ -392,12 +589,90 @@ begin
end; end;
//sqlite3_finalize(vm); //sqlite3_finalize(vm);
javaEnvRef^^.CallVoidMethod(javaEnvRef, dbCursor, FDBCursor_close); javaEnvRef^^.CallVoidMethod(javaEnvRef, dbCursor, FDBCursor_close);
RealInternalCloseHandle();
end; end;
function TSqliteJNIDataset.GetRowsAffected: Integer; 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 begin
DebugLn('[TSqliteJNIDataset.GetRowsAffected]'); DebugLn(Format('[TSqliteJNIDataset.SqliteExec] AndroidDB=%x ACallback=%x Data=%x ASQL=%s',
//f/Result := sqlite3_changes(FSqliteHandle); [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
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;
procedure TSqliteJNIDataset.PrepareReturnString;
var
exceptionObj: jthrowable;
javaLangClass: jclass;
javaLangClass_getName: JMethodID;
lJavaString: JString;
lNativeString: PChar;
begin
FReturnString := '';
// There seams to be no way to get any information about the exception in JNI =(
//DebugLn('[TSqliteJNIDataset.PrepareReturnString] START');
if javaEnvRef^^.ExceptionCheck(javaEnvRef) = JNI_FALSE then
begin
DebugLn('[TSqliteJNIDataset.PrepareReturnString] No exception found');
Exit;
end;
exceptionObj := javaEnvRef^^.ExceptionOccurred(javaEnvRef);
javaEnvRef^^.ExceptionDescribe(javaEnvRef);
javaEnvRef^^.ExceptionClear(javaEnvRef);
{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; end;
procedure TSqliteJNIDataset.ExecuteDirect(const ASQL: String); procedure TSqliteJNIDataset.ExecuteDirect(const ASQL: String);
@ -407,6 +682,7 @@ var
lJavaString: JString; lJavaString: JString;
begin begin
DebugLn('[TSqliteJNIDataset.ExecuteDirect] ' + ASQL); DebugLn('[TSqliteJNIDataset.ExecuteDirect] ' + ASQL);
RealInternalGetHandle();
// void execSQL(String sql) // void execSQL(String sql)
// preparations // preparations
@ -419,6 +695,8 @@ begin
// clean up // clean up
javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString); javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
RealInternalCloseHandle();
{FReturnCode := sqlite3_prepare(FSqliteHandle, Pchar(ASQL), -1, @vm, nil); {FReturnCode := sqlite3_prepare(FSqliteHandle, Pchar(ASQL), -1, @vm, nil);
if FReturnCode <> SQLITE_OK then if FReturnCode <> SQLITE_OK then
DatabaseError(ReturnString, Self); DatabaseError(ReturnString, Self);
@ -426,194 +704,6 @@ begin
sqlite3_finalize(vm);} sqlite3_finalize(vm);}
end; 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;');
//
// Methods from FDBClosable
//
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.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);
{ //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_TRUE;
while not lIsAfterLastRow do
begin
Inc(FRecordCount);
New(TempItem^.Next);
TempItem^.Next^.Previous := TempItem;
TempItem := TempItem^.Next;
GetMem(TempItem^.Row, FRowBufferSize);
for Counter := 0 to ColumnCount - 1 do
begin
// TempItem^.Row[Counter] := StrNew(sqlite3_column_text(vm, Counter));
// public abstract String getString (int columnIndex)
lJavaString := javaEnvRef^^.CallObjectMethod(javaEnvRef, dbCursor, FDBCursor_getString);
lNativeString := javaEnvRef^^.GetStringUTFChars(javaEnvRef, lJavaString, nil);
TempItem^.Row[Counter] := StrNew(lNativeString);
javaEnvRef^^.ReleaseStringUTFChars(javaEnvRef, lJavaString, lNativeString);
javaEnvRef^^.DeleteLocalRef(javaEnvRef, lJavaString);
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_TRUE;
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;
end;
function TSqliteJNIDataset.GetLastInsertRowId: Int64;
begin
Result := FLastInsertRowId;
DebugLn('[TSqliteJNIDataset.GetLastInsertRowId] Result='+IntToStr(Result));
//f/Result := sqlite3_last_insert_rowid(FSqliteHandle);
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;
function TSqliteJNIDataset.QuickQuery(const ASQL: String; const AStrList: TStrings; FillObjects:Boolean): String; function TSqliteJNIDataset.QuickQuery(const ASQL: String; const AStrList: TStrings; FillObjects:Boolean): String;
var var
vm: Pointer; vm: Pointer;
@ -659,5 +749,21 @@ begin
sqlite3_finalize(vm); } sqlite3_finalize(vm); }
end; 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. end.