diff --git a/src/Runtime/XSharp.SQLRdd/Classes/Command.prg b/src/Runtime/XSharp.SQLRdd/Classes/Command.prg
index 9f39d8b4e2..3c589696fb 100644
--- a/src/Runtime/XSharp.SQLRdd/Classes/Command.prg
+++ b/src/Runtime/XSharp.SQLRdd/Classes/Command.prg
@@ -32,7 +32,7 @@ class SqlDbCommand inherit SqlDbHandleObject implements IDisposable
/// The DBMS Provider class.
property Provider as ISqlDbProvider get iif(Connection == null, null, Connection:Provider)
/// The text of the Command object.
- property CommandText as string get DbCommand:CommandText set DbCommand:CommandText := value
+ property CommandText as string get iif(DbCommand == null, "", DbCommand:CommandText) set DbCommand:CommandText := value
#endregion
diff --git a/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg b/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg
index d34472caf7..f5fb262b7b 100644
--- a/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg
+++ b/src/Runtime/XSharp.SQLRdd/Classes/Connection.prg
@@ -105,6 +105,8 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
internal const DefaultConnection := "DEFAULT" as string
internal static Connections as List
static internal property DefaultCached as logic auto
+ private static oExistingTables := List{} as List
+
static constructor()
Connections := List{}
DefaultCached := true
@@ -273,10 +275,7 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
KeepOpen := DefaultCached
if @@Callback != null
self:CallBack += @@Callback
- SELF:MetadataProvider := SqlMetadataProviderCallBack{SELF}
- ELSE
- SELF:MetadataProvider := SqlMetadataProviderIni{SELF}
- endif
+ endif
Connections:Add(self)
_datasourceProperties := Dictionary{StringComparer.OrdinalIgnoreCase}
_commands := List{}
@@ -284,9 +283,7 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
self:ForceOpen()
SELF:_FillMetadataCollections()
SELF:_FillDataSourceProperties()
- SELF:_CheckConnectionTable()
SELF:_CreateLockTable()
- SELF:_Login()
SELF:InitializeLockTimer()
// Todo: Check for # of open users and close the connection when no users are left and then throw an exception
return
@@ -303,9 +300,11 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
if self:RDDs:Count > 0
return false
endif
- self:_Logout()
// Logout the workstation from the Open Connections table
- _command:Dispose()
+ if _command != null
+ _command:Dispose()
+ endif
+
foreach var cmd in SELF:_commands:ToArray()
if cmd:Connection == self
cmd:Dispose()
@@ -480,7 +479,7 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
/// Table name to display for Event Handler
/// DbDataReader or NULL when an exception occurred
method ExecuteReader(cCommand as string, cTable := __FUNCTION__ as STRING) as DbDataReader
- local result := null as DbDataReader
+ local result := null as DbDataReader
try
_command:CommandText := cCommand
result := _command:ExecuteReader(cTable)
@@ -627,6 +626,9 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
/// TRUE when the table exists.
method DoesTableExist(cTableName as string) as logic
try
+ if oExistingTables:Contains(cTableName)
+ return true
+ endif
if !SELF:HasCollection(TABLECOLLECTION)
return false
endif
@@ -634,12 +636,16 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
local aTableRestrictions := string[]{4} as string[]
aTableRestrictions[2] := cTableName
var dt := self:DbConnection:GetSchema(TABLECOLLECTION, aTableRestrictions)
- return dt:Rows:Count > 0
+ if dt:Rows:Count > 0
+ oExistingTables:Add(cTableName)
+ return true
+ endif
else
var dt := self:DbConnection:GetSchema(TABLECOLLECTION)
foreach row as DataRow in dt:Rows
var tbl := row["TABLE_NAME"]:ToString()
if String.Compare(tbl, cTableName, true) == 0
+ oExistingTables:Add(cTableName)
return true
endif
next
@@ -737,58 +743,6 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
end method
#endregion
-
-
- private method _CheckConnectionTable() as void
- if SELF:DoesTableExist(CONNECTIONSTABLE)
- return
- endif
- var sb := StringBuilder{}
- sb:Append(SELF:Provider:CreateTableStatement)
- using var cmd := SqlDbCommand{"ConnectionTable", self, false}
-
- sb:Replace(SqlDbProvider.TableNameMacro,CONNECTIONSTABLE)
- sb:Replace(SqlDbProvider.FieldDefinitionListMacro, "station varchar(50), username varchar(50), lastlogin varchar(10), refcount int")
- cmd:CommandText := sb:ToString()
- cmd:ExecuteNonQuery("License")
-
-
- private method _Login() as void
- SELF:_LoginWorker(true)
- private method _Logout() as void
- SELF:_LoginWorker(false)
- private method _LoginWorker(lIn as LOGIC) as void
- var user := Environment.UserName
- var station := Environment.MachineName
- var dDate := DateTime.Now
- var today := dDate:Year:ToString()+"-"+dDate:Month:ToString("0#")+"-"+dDate:Day:ToString("0#")
- SELF:BeginTrans()
- // clear old logins
- using var cmd := SqlDbCommand{"Licenses", self, false}
- cmd:CommandText := "Delete from "+CONNECTIONSTABLE+" where lastlogin < @p3"
- cmd:ClearParameters()
- cmd:AddParameter("@p1",user)
- cmd:AddParameter("@p2",station)
- cmd:AddParameter("@p3",today)
- cmd:ExecuteNonQuery("License")
- var sWhere := "where username = @p1 and station = @p2 and lastlogin = @p3"
- if lIn
- cmd:CommandText := "select count(*) from "+CONNECTIONSTABLE+" "+sWhere
- var result := Convert.ToInt64(cmd:ExecuteScalar())
- if result == 0
- cmd:CommandText := "Insert into "+CONNECTIONSTABLE+"(username, station, lastlogin, refcount) values(@p1, @p2, @p3, 0)"
- cmd:ExecuteNonQuery("Login")
- endif
- cmd:CommandText := "Update "+CONNECTIONSTABLE+" set refcount = refcount + 1 "+sWhere
- cmd:ExecuteNonQuery("Login")
- else
- cmd:CommandText := "Update "+CONNECTIONSTABLE+" set refcount = refcount - 1 "+sWhere
- cmd:ExecuteNonQuery("Logout")
- cmd:CommandText := "delete from "+CONNECTIONSTABLE+" "+sWhere+" and refcount <= 0"
- cmd:ExecuteNonQuery("Logout")
-
- endif
- SELF:CommitTrans()
#region Implement IDisposable
///
public override method Dispose() as void
@@ -798,7 +752,6 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
#endregion
- INTERNAL CONST CONNECTIONSTABLE := "xs_connections" as string
INTERNAL CONST DEFAULT_ALLOWUPDATES := TRUE AS LOGIC
INTERNAL CONST DEFAULT_COMPAREMEMO := TRUE AS LOGIC
INTERNAL CONST DEFAULT_DELETEDCOLUMN := "" AS STRING
@@ -913,6 +866,9 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
// Refresh my Locks
using var cmdRefresh := SqlDbCommand{"RefreshLockTable", SELF, false}
+ if !cmdRefresh:Connection:IsOpen
+ cmdRefresh:Connection:ForceOpen()
+ endif
var updateStatement := SELF:Provider:UpdateStatement:Replace(SqlDbProvider.TableNameMacro, LockTableName)
updateStatement := updateStatement:Replace(SqlDbProvider.ColumnsMacro, "lockdatetime = " + SELF:Provider:CurrentDateTime)
updateStatement := updateStatement:Replace(SqlDbProvider.WhereMacro, "connectionid = " + parameterName1)
@@ -923,6 +879,9 @@ class SqlDbConnection inherit SqlDbHandleObject implements IDisposable
// Clear all old locks
using var cmdClear := SqlDbCommand{"ClearLockTable", SELF, false}
+ if !cmdClear:Connection:IsOpen
+ cmdClear:Connection:ForceOpen()
+ endif
cmdClear:CommandText := SELF:Provider:DeleteStatement:Replace(SqlDbProvider.TableNameMacro, LockTableName):Replace(SqlDbProvider.WhereMacro, "LockDateTime < " + parameterName1)
cmdClear:Parameters := List{}
cmdClear:Parameters:Add(SqlDbParameter{parameterName1, DateTime.Now.AddSeconds(-120)})
diff --git a/src/Runtime/XSharp.SQLRdd/RDD/SQLDbOrderBag.prg b/src/Runtime/XSharp.SQLRdd/RDD/SQLDbOrderBag.prg
index 0bbb3eefe6..f0379efe92 100644
--- a/src/Runtime/XSharp.SQLRdd/RDD/SQLDbOrderBag.prg
+++ b/src/Runtime/XSharp.SQLRdd/RDD/SQLDbOrderBag.prg
@@ -58,7 +58,7 @@ internal class SqlDbOrderBag INHERIT BaseIndex
super(oRdd)
self:RDD := oRdd
SELF:LogicalName := cName
- if File(cName)
+ if System.IO.File.Exists(cName)
cName := FPathName()
SELF:Path := System.IO.Path.GetDirectoryName(cName)
SELF:FileName := System.IO.Path.GetFileNameWithoutExtension(cName)
diff --git a/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Main.prg b/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Main.prg
index fa5899c044..522c8f3d4b 100644
--- a/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Main.prg
+++ b/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Main.prg
@@ -41,7 +41,7 @@ partial class SQLRDD inherit Workarea
public property RowCount as INT => iif(Self:DataTable == null, 0, Self:DataTable:Rows:Count)
/// The current rownumber in the buffer (DataTable).
- public property RowNumber as INT GET _rowNumber INTERNAL SET _rowNumber := value
+ public property RowNumber as INT GET _rowNumber private SET _rowNumber := value
/// The current row in the buffer (DataTable).
/// When the server is at EOF then the phantomrow is returned.
@@ -148,7 +148,7 @@ partial class SQLRDD inherit Workarea
throw Exception{}
endif
if !self:_oTd:HasRecnoColumn
- throw Exception{"RecnoColumn is required"}
+ throw Exception{"RecnoColumn is required for table " + info:FileName}
endif
endif
local aUpdatableColumns := null as HashSet
@@ -274,7 +274,7 @@ partial class SQLRDD inherit Workarea
next
self:_ExecuteInsertStatement(row)
row:AcceptChanges()
- _updatedRowIds:Add((int)row[self:_recnoColumNo])
+ _updatedRecNos:Add((int)row[self:_recnoColumNo])
SELF:RowNumber := SELF:DataTable:Rows:Count
self:_serverReccount += 1
SELF:_SetBOF(FALSE)
@@ -368,8 +368,8 @@ partial class SQLRDD inherit Workarea
var col := self:_GetColumn(nFldPos)
if SELF:_updatableColumns:Contains(col)
var row := SELF:CurrentRow
- if !_updatedRowIds:Contains((int)row[self:_recnoColumNo])
- _updatedRowIds:Add((int)row[self:_recnoColumNo])
+ if !_updatedRecNos:Contains((int)row[self:_recnoColumNo])
+ _updatedRecNos:Add((int)row[self:_recnoColumNo])
endif
row[nFldPos-1] := SELF:_HandleNullDate(oValue,self:DataTable:Columns[nFldPos-1])
@@ -385,7 +385,6 @@ partial class SQLRDD inherit Workarea
/// Write the contents of a work area's memory to the data store (usually a disk).
///
override method GoCold() as logic
- local row as DataRow
var current := SELF:CurrentRow
if current == null
return false
@@ -404,64 +403,8 @@ partial class SQLRDD inherit Workarea
return false
endif
- foreach var rowId in _updatedRowIds
- try
- foreach tableRow as DataRow in self:DataTable:Rows
- if (int)tableRow[self:_recnoColumNo] = rowId
- row := tableRow
- endif
- next
-
- // Check row lock
- dbLockInfo:RecId := row[_oTd:RecnoColumn]
- SELF:CheckLock(dbLockInfo, StringBuilder{}, myLock, otherLock)
- if otherLock
- lOk := false
- loop
- endif
-
- if !myLock
- SELF:Lock(ref dbLockInfo)
- endif
-
- lOk := true
- if super:Deleted
- local wasNew := false as logic
- // Append from may add deleted rows
- if row:RowState.HasFlag(DataRowState.Added)
- lOk := SELF:_ExecuteInsertStatement(row)
- row:AcceptChanges()
- wasNew := true
- endif
- if self:_deletedColumnNo > -1
- if !wasNew
- // already written with _deletedColumnNo with the correct value
- lOk := SELF:_ExecuteUpdateStatement(row)
- if lOk
- row:AcceptChanges()
- endif
- endif
- else
- lOk := SELF:_ExecuteDeleteStatement(row)
- // we do not clear the fields, but leave the row unchanged.
- // the DBF has the deleted flag. This emulates what DBF files do
-
- row:AcceptChanges()
- endif
-
- else
- if row:RowState.HasFlag(DataRowState.Added)
- lOk := SELF:_ExecuteInsertStatement(row)
- row:AcceptChanges()
- elseif row:RowState.HasFlag(DataRowState.Modified)
- lOk := SELF:_ExecuteUpdateStatement(row)
- row:AcceptChanges()
- endif
- endif
- catch e as Exception
- lOk := false
- self:_dbfError(ERDD.WRITE, XSharp.Gencode.EG_WRITE, "SqlRDD:GoCold", e:Message )
- end try
+ foreach var rowId in _updatedRecNos
+ lOk := self:_UpdateRow(rowId)
if !lOk
exit
endif
@@ -472,7 +415,7 @@ partial class SQLRDD inherit Workarea
else
self:DataTable:RejectChanges()
endif
- _updatedRowIds:Clear()
+ _updatedRecNos:Clear()
self:_GetRecCount()
endif
return lOk
@@ -487,12 +430,12 @@ partial class SQLRDD inherit Workarea
override method Close() as logic
local lOk as logic
lOk := SELF:GoCold()
+ self:UnLock(0)
// This method deletes the temporary file after the file is closed
foreach var bag in self:OrderBagList
bag:Close()
next
- self:UnLock(0)
- _connection:UnregisterRdd(self)
+ _connection?:UnregisterRdd(self)
lOk := super:Close()
return lOk
@@ -653,7 +596,7 @@ partial class SQLRDD inherit Workarea
LOCAL result AS LOGIC
TRY
VAR nRec := Convert.ToUInt32( oRec )
- if nRec != RowNumber
+ if oRec != self:CurrentRow[self:_recnoColumNo]
result := SELF:GoTo( (DWORD) nRec )
endif
CATCH ex AS Exception
@@ -780,7 +723,6 @@ partial class SQLRDD inherit Workarea
end try
endif
else
- self:_rowNumber := self:RowNumber
return super:Deleted
endif
end get
@@ -901,6 +843,10 @@ partial class SQLRDD inherit Workarea
end method
public override method UnLock(oRecId as object) as logic
+ if Connection?:Provider is null .or. !self:Connection:IsOpen
+ return false
+ endif
+
try
var sb := StringBuilder{}
var sbWhere := StringBuilder{}
@@ -923,7 +869,7 @@ partial class SQLRDD inherit Workarea
cmd:AddParameter(self:Provider:ParameterPrefix+"p1", Environment.MachineName ?? String.Empty)
cmd:AddParameter(self:Provider:ParameterPrefix+"p2", Environment.UserName ?? String.Empty)
cmd:AddParameter(self:Provider:ParameterPrefix+"p3", self:Connection:ConnectionId:ToString())
- cmd:AddParameter(self:Provider:ParameterPrefix+"p4", _oTd:RealName ?? String.Empty)
+ cmd:AddParameter(self:Provider:ParameterPrefix+"p4", _oTd?:RealName ?? super:Alias ?? String.Empty)
cmd:AddParameter(self:Provider:ParameterPrefix+"p5", (int)super:Area)
if oRecId != null .and. oRecId is int .and. ((int)oRecId) > 0
cmd:AddParameter(self:Provider:ParameterPrefix+"p6", (int)oRecId)
@@ -934,9 +880,15 @@ partial class SQLRDD inherit Workarea
end try
// In ADS this happens in here: ACE.AdsUnlockRecord(SELF:_Table, dwRecno)
- if (self:Deleted)
+ if self:Deleted
self:_ExecuteDeleteStatement(CurrentRow)
endif
+ if self:_updatedRecNos.Count > 0
+ foreach var nRecNo in self:_updatedRecNos
+ self:_UpdateRow(nRecNo)
+ next
+ self:_updatedRecNos:Clear()
+ endif
return true
end method
diff --git a/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Private.prg b/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Private.prg
index 43dc8e9a67..2df7f5b813 100644
--- a/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Private.prg
+++ b/src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Private.prg
@@ -39,7 +39,7 @@ partial class SQLRDD
private _emptyValues as object[]
private _updatableColumns as List
private _keyColumns as List
- private _updatedRowIds as List
+ private _updatedRecNos as List
private _orderBagList as List
private _rowNumber as long
@@ -59,7 +59,7 @@ partial class SQLRDD
#region Properties
internal property Connection as SqlDbConnection get _connection
- internal property Provider as ISqlDbProvider get _connection:Provider
+ internal property Provider as ISqlDbProvider get _connection?:Provider
internal property Command as SqlDbCommand get _command
internal property OrderBagList as List get _orderBagList
internal property CurrentPage as int => _currentPageNo
@@ -127,7 +127,7 @@ partial class SQLRDD
SELF:_currentPageNo := 1
SELF:_firstPageNo := 1
self:_trimValues := true // trim String Valuess
- SELF:_updatedRowIds := List{}
+ SELF:_updatedRecNos := List{}
SELF:_keyColumns := List{}
SELF:_updatableColumns := List{}
SELF:_orderBagList := List{}
@@ -607,26 +607,33 @@ partial class SQLRDD
return result
PRIVATE METHOD _GotoRecord(nRec as DWORD) AS LOGIC
- if SELF:DataTable:Rows:Count < 1
- // Brute walk
- SELF:_command:CommandText := _builder:BuildRowNumberStatement(nRec)
- var result := SELF:_command:ExecuteScalar(SELF:_oTd:Name)
- var iResult := Convert.ToInt64(result)
- // shouldn't this be ToUInt32?
-
- // determine correct page
- SELF:_currentPageNo := (INT) ((iResult - 1) / SELF:_oTd:PageSize) + 1
- SELF:_ClearTable()
- SELF:DataTable := SELF:_ReadTable("")
- end if
-
- // locate the row in the page
- SELF:RowNumber := 1
- DO WHILE SELF:RowNumber <= SELF:DataTable:Rows:Count
- IF SELF:RecNo == nRec
+
+ // if record to go to is already in loaded page
+ if self:_GotoRecordInPage(nRec)
+ return true
+ endif
+
+ // Brute walk
+ SELF:_command:CommandText := _builder:BuildRowNumberStatement(nRec)
+ var result := SELF:_command:ExecuteScalar(SELF:_oTd:Name)
+ var iResult := Convert.ToInt64(result)
+
+ // determine correct page
+ SELF:_currentPageNo := (INT) ((iResult - 1) / SELF:_oTd:PageSize) + 1
+ SELF:_ClearTable()
+ SELF:DataTable := SELF:_ReadTable("")
+
+ RETURN self:_GotoRecordInPage(nRec)
+
+ PRIVATE METHOD _GotoRecordInPage(nRec as DWORD) AS LOGIC
+ LOCAL nRowNumber AS INT
+ nRowNumber := 1
+ DO WHILE nRowNumber <= SELF:DataTable:Rows:Count
+ IF nRec == Convert.ToInt32(SELF:DataTable:Rows[nRowNumber-1][self:_recnoColumNo])
+ SELF:RowNumber := nRowNumber
RETURN TRUE
ENDIF
- SELF:RowNumber+= 1
+ nRowNumber += 1
ENDDO
RETURN FALSE
@@ -654,6 +661,71 @@ partial class SQLRDD
SELF:_CheckEofBof()
RETURN TRUE
+ PRIVATE METHOD _UpdateRow(nRecNo AS INT) AS LOGIC
+ local row as DataRow
+ local lOk := TRUE as logic
+ try
+ foreach tableRow as DataRow in self:DataTable:Rows
+ if (int)tableRow[self:_recnoColumNo] = nRecNo
+ row := tableRow
+ endif
+ next
+
+ // Check row lock
+ var dbLockInfo := DbLockInfo{}
+ dbLockInfo:RecId := row[_oTd:RecnoColumn]
+ var myLock := false
+ var otherLock := false
+ SELF:CheckLock(dbLockInfo, StringBuilder{}, myLock, otherLock)
+ if otherLock
+ return false
+ endif
+
+ if !myLock
+ SELF:Lock(ref dbLockInfo)
+ endif
+
+ lOk := true
+ if super:Deleted
+ local wasNew := false as logic
+ // Append from may add deleted rows
+ if row:RowState.HasFlag(DataRowState.Added)
+ lOk := SELF:_ExecuteInsertStatement(row)
+ row:AcceptChanges()
+ wasNew := true
+ endif
+ if self:_deletedColumnNo > -1
+ if !wasNew
+ // already written with _deletedColumnNo with the correct value
+ lOk := SELF:_ExecuteUpdateStatement(row)
+ if lOk
+ row:AcceptChanges()
+ endif
+ endif
+ else
+ lOk := SELF:_ExecuteDeleteStatement(row)
+ // we do not clear the fields, but leave the row unchanged.
+ // the DBF has the deleted flag. This emulates what DBF files do
+
+ row:AcceptChanges()
+ endif
+
+ else
+ if row:RowState.HasFlag(DataRowState.Added)
+ lOk := SELF:_ExecuteInsertStatement(row)
+ row:AcceptChanges()
+ elseif row:RowState.HasFlag(DataRowState.Modified)
+ lOk := SELF:_ExecuteUpdateStatement(row)
+ row:AcceptChanges()
+ endif
+ endif
+ catch e as Exception
+ lOk := false
+ self:_dbfError(ERDD.WRITE, XSharp.Gencode.EG_WRITE, "SqlRDD:GoCold", e:Message )
+ end try
+
+ RETURN lOk
+
PRIVATE METHOD LockRecNo(lockInfo ref DbLockInfo) AS INT
var lockRecNo := 0
if lockInfo:Method != XSharp.RDD.Support.DbLockInfo.LockMethod.File