Skip to content
52 changes: 52 additions & 0 deletions src/Runtime/XSharp.SQLRdd/Classes/Command.prg
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,58 @@ class SqlDbCommand inherit SqlDbHandleObject implements IDisposable
end try
end method

/// <summary>
/// Execute the command and return the rows in a copy of oTemplate's structure.
/// </summary>
/// <param name="cTable">The name of the DataTable (when the template cannot be used)</param>
/// <param name="oTemplate">An empty table with the columns the command returns, or NULL</param>
/// <remarks>
/// Building the structure from the reader's schema (DataTable.Load()) is the expensive part of
/// a query that returns one or a few rows of a wide table: 1.5 ms against 0.5 ms for 178
/// columns. The RDD runs every query of a work area with the same column list, so it passes
/// the structure of its buffer. When the reader's columns do not match the template, the
/// rows are loaded as by GetDataTable(cTable).
/// </remarks>
method GetDataTable(cTable as string, oTemplate as DataTable) as DataTable
if oTemplate == null
return SELF:GetDataTable(cTable)
endif
try
using var reader := SELF:ExecuteReader(cTable)
if reader == null
return null
endif
var lMatch := reader:FieldCount == oTemplate:Columns:Count
for var i := 0 to reader:FieldCount-1
if !lMatch
exit
endif
lMatch := String.Equals(reader:GetName(i), oTemplate:Columns[i]:ColumnName, StringComparison.OrdinalIgnoreCase)
next
local oDataTable as DataTable
if lMatch
oDataTable := oTemplate:Clone()
var aValues := object[]{reader:FieldCount}
oDataTable:BeginLoadData()
do while reader:Read()
reader:GetValues(aValues)
oDataTable:LoadDataRow(aValues, true)
enddo
oDataTable:EndLoadData()
else
oDataTable := DataTable{iif(String.IsNullOrEmpty(cTable), SELF:Name, cTable)}
var oDataSet := DataSet{}
oDataSet:EnforceConstraints := false
oDataSet:Tables:Add(oDataTable)
oDataTable:Load(reader)
endif
return oDataTable
catch e as Exception
Connection:LastException := e
return null
end try
end method

/// <summary>
/// Execute a scalar command
/// </summary>
Expand Down
29 changes: 27 additions & 2 deletions src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Main.prg
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,11 @@ partial class SQLRDD inherit Workarea
if Connection?:Provider is null .or. !self:Connection:IsOpen .or. _oTd == null
return recNos:ToArray()
endif
if !self:Connection:UseLocks
// No lock table entries are ever written without UseLocks. Code that asks for the
// lock list after every movement (RLockList/IsLocked()) paid a query each time.
return recNos:ToArray()
endif
try
var sb := StringBuilder{}
sb:AppendLine("select recno from " + SqlDbConnection.LockTableName)
Expand Down Expand Up @@ -776,7 +781,7 @@ partial class SQLRDD inherit Workarea
IF nRec == 0
// DBF semantics: GoTo(0) positions on the phantom record (EOF) without any I/O.
// Browsers do this for every empty row they paint, so it must not cost a query.
SELF:_GoToPhantom()
SELF:_GoToPhantom(TRUE)
RETURN TRUE
ENDIF
SELF:_eofPending := FALSE
Expand Down Expand Up @@ -804,7 +809,27 @@ partial class SQLRDD inherit Workarea
next
endif

var found := SELF:_GotoRecord(nRec)
// Code that saves the record number, seeks elsewhere and goes back (or follows a
// relation) mostly just reads the record's fields afterwards. Its position in the order
// is only needed when something moves from it (Skip(), OrderKeyNo ...), see
// _EnsurePositioned(): load just the record by its recno. Looking the position up right
// away cost a ROW_NUMBER() over the order plus a whole page around the record.
// When the previous GoTo() already loaded just one record this way, position fully: a
// browse that calls GoTo() for every row it paints then gets a page with the neighbouring
// rows instead of one query per row. (A single record left by an order change does not
// count - SetOrder() followed by GoTo(saved record) is the usual restore pattern.)
local found as logic
if SELF:_tableMode == TableMode.Table .and. SELF:_recnoColumNo >= 0 .and. SELF:DataTable != null ;
.and. !(SELF:_lazyGoto .and. !SELF:_bufferContiguous .and. SELF:RowCount <= 1)
found := SELF:_LoadSingleRecord(nRec)
if found
SELF:_lazyGoto := TRUE
else
found := SELF:_GotoRecord(nRec)
endif
else
found := SELF:_GotoRecord(nRec)
endif
SELF:_CheckEofBof()
RETURN found
end method
Expand Down
20 changes: 17 additions & 3 deletions src/Runtime/XSharp.SQLRdd/RDD/SQLRDD-Orders.prg
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,13 @@ partial class SQLRDD
// index/order" pattern used throughout the app, and it must keep flushing even when
// the order doesn't actually change.
SELF:GoCold()
// The record we stand on (just written by GoCold()) is kept across the order change
// instead of being read again, and LastRec() does not change with the order.
local aCurrentRow := null as object[]
if !lWasEof .and. SELF:_hasData .and. SELF:DataTable != null .and. SELF:RowNumber >= 1 .and. SELF:RowNumber <= SELF:RowCount
aCurrentRow := SELF:CurrentRow:ItemArray
endif
var lTableCountValid := SELF:_tableReccountValid
if (orderInfo:Order is long var nOrder0 .and. nOrder0 == 0) .or. ;
(orderInfo:Order is string var cOrder0 .and. String.IsNullOrEmpty(cOrder0))
// Order 0 (or an empty order name) means: switch back to the natural/physical
Expand All @@ -211,8 +218,9 @@ partial class SQLRDD
self:_CloseCursor()
endif
SELF:CurrentOrder := null
self:_tableReccountValid := lTableCountValid
if lReset
self:_RepositionAfterOrderChange(currentRecord, lWasEof)
self:_RepositionAfterOrderChange(currentRecord, lWasEof, aCurrentRow)
else
self:GoTo(currentRecord)
endif
Expand All @@ -224,6 +232,7 @@ partial class SQLRDD
self:_CloseCursor()
endif
SELF:CurrentOrder := newOrder
self:_tableReccountValid := lTableCountValid
result := CurrentOrder != null
IF result
SELF:CurrentOrder:ClearCache()
Expand All @@ -232,7 +241,7 @@ partial class SQLRDD
// loaded row. _CloseCursor() above cleared it, so without this there would
// be nothing to evaluate.
if lReset
self:_RepositionAfterOrderChange(currentRecord, lWasEof)
self:_RepositionAfterOrderChange(currentRecord, lWasEof, aCurrentRow)
else
self:GoTo(currentRecord)
endif
Expand Down Expand Up @@ -569,7 +578,12 @@ partial class SQLRDD
// null instead of an empty table - treat that the same as "no rows found" instead of
// crashing on DataTable:Rows below, same fix as GoTo()/_ClearTable() already got.
IF (SELF:DataTable == null .or. SELF:DataTable:Rows:Count = 0) .and. !seekInfo.SoftSeek
SELF:GoTo(0)
// BOF is set below, so no need to count the order for it (GoTo(0) would)
IF SELF:DataTable == null
SELF:GoTo(0)
ELSE
SELF:_GoToPhantom(FALSE)
ENDIF
SELF:_Found := false
SELF:_SetEOF(true)
SELF:_SetBOF(false)
Expand Down
Loading