Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Comment: | {4164} several fixes and enhancements to TSQLTable |
---|---|
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
58246461d316bf4184984fb808e7bc9a |
User & Date: | ab 2018-01-24 11:50:39 |
2018-01-24
| ||
15:48 | {4165} minor TSQLTable fixes - no new feature check-in: 1187eb2d68 user: ab tags: trunk | |
11:50 | {4164} several fixes and enhancements to TSQLTable check-in: 58246461d3 user: ab tags: trunk | |
11:49 | {4163} fixed some compilation errors introducing by latest commit check-in: 60e6ff1900 user: ab tags: trunk | |
Changes to SQLite3/mORMot.pas.
8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 8166 .... 8567 8568 8569 8570 8571 8572 8573 8574 8575 8576 8577 8578 8579 8580 8581 .... 8618 8619 8620 8621 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 ..... 25286 25287 25288 25289 25290 25291 25292 25293 25294 25295 25296 25297 25298 25299 25300 25301 25302 25303 25304 25305 25306 25307 25308 ..... 25310 25311 25312 25313 25314 25315 25316 25317 25318 25319 25320 25321 25322 25323 25324 25325 25326 25327 25328 25329 25330 25331 25332 25333 25334 25335 ..... 25337 25338 25339 25340 25341 25342 25343 25344 25345 25346 25347 25348 25349 25350 25351 25352 25353 25354 25355 25356 25357 25358 25359 25360 25361 25362 25363 25364 25365 25366 25367 25368 25369 25370 25371 25372 25373 25374 25375 25376 25377 25378 25379 25380 25381 25382 25383 25384 25385 25386 25387 25388 25389 25390 25391 25392 25393 25394 25395 25396 25397 25398 25399 25400 25401 25402 25403 25404 25405 ..... 25770 25771 25772 25773 25774 25775 25776 25777 25778 25779 25780 25781 25782 25783 25784 25785 25786 25787 ..... 25792 25793 25794 25795 25796 25797 25798 25799 25800 25801 25802 25803 25804 25805 25806 25807 25808 25809 25810 25811 25812 25813 25814 25815 25816 25817 25818 25819 25820 25821 25822 25823 25824 25825 25826 25827 25828 25829 25830 25831 25832 25833 25834 25835 25836 25837 25838 25839 25840 25841 25842 25843 25844 25845 25846 25847 25848 25849 25850 25851 25852 25853 ..... 25924 25925 25926 25927 25928 25929 25930 25931 25932 25933 25934 25935 25936 25937 25938 25939 25940 25941 25942 25943 25944 25945 25946 25947 25948 25949 25950 25951 25952 25953 25954 ..... 25956 25957 25958 25959 25960 25961 25962 25963 25964 25965 25966 25967 25968 25969 25970 25971 25972 25973 25974 25975 25976 25977 25978 25979 25980 25981 25982 25983 25984 25985 25986 25987 25988 25989 25990 25991 25992 25993 ..... 26027 26028 26029 26030 26031 26032 26033 26034 26035 26036 26037 26038 26039 26040 26041 26042 ..... 26052 26053 26054 26055 26056 26057 26058 26059 26060 26061 26062 26063 26064 26065 26066 26067 26068 26069 26070 26071 26072 26073 26074 26075 26076 26077 26078 26079 26080 26081 26082 26083 26084 26085 26086 26087 26088 26089 26090 26091 26092 26093 26094 26095 26096 26097 26098 26099 26100 26101 26102 26103 26104 26105 26106 ..... 27222 27223 27224 27225 27226 27227 27228 27229 27230 27231 27232 27233 27234 27235 27236 ..... 27417 27418 27419 27420 27421 27422 27423 27424 27425 27426 27427 27428 27429 27430 27431 ..... 27443 27444 27445 27446 27447 27448 27449 27450 27451 27452 27453 27454 27455 27456 27457 ..... 27508 27509 27510 27511 27512 27513 27514 27515 27516 27517 27518 27519 27520 27521 27522 27523 27524 27525 ..... 28581 28582 28583 28584 28585 28586 28587 28588 28589 28590 28591 28592 28593 28594 28595 ..... 28605 28606 28607 28608 28609 28610 28611 28612 28613 28614 28615 28616 28617 28618 28619 ..... 50504 50505 50506 50507 50508 50509 50510 50511 50512 50513 50514 50515 50516 50517 50518 50519 50520 50521 |
TOnSQLTableGetValue = function(Sender: TSQLTable; Row, Field: integer; HumanFriendly: boolean): RawJSON of object; /// store TSQLFieldType and RTTI for a given TSQLTable field TSQLTableFieldType = record /// the field kind, as in JSON (match TSQLPropInfo.SQLFieldTypeStored) ContentType: TSQLFieldType; /// the field size in bytes; -1 means not computed yet ContentSize: integer; /// used for sftEnumerate, sftSet and sftBlobDynArray fields ContentTypeInfo: pointer; /// the corresponding index in fQueryTables[] TableIndex: integer; end; /// wrapper to an ORM result table, staticaly stored as UTF-8 text // - contain all result in memory, until destroyed // - first row contains the field names // - following rows contains the data itself // - GetString() can be used in a TDrawString // - will be implemented as TSQLTableJSON for remote access through optimized ................................................................................ /// guess the field type from first non null data row // - if QueryTables[] are set, exact field type and (enumerate) TypeInfo() is // retrieved from the Delphi RTTI; otherwise, get from the cells content // - return sftUnknown is all data fields are null // - sftBlob is returned if the field is encoded as SQLite3 BLOB literals // (X'53514C697465' e.g.) // - since TSQLTable data is PUTF8Char, string type is sftUTF8Text only function FieldType(Field: integer; out FieldTypeInfo: TSQLTableFieldType): TSQLFieldType; overload; /// get the appropriate Sort comparaison function for a field, // nil if not available (bad field index or field is blob) // - field type is guessed from first data row function SortCompare(Field: integer): TUTF8Compare; /// get the mean of characters length of all fields // - the character length is for the first line of text only (stop counting // at every newline character, i.e. #10 or #13 char) ................................................................................ // - you can define a specific type for a given column, and optionally // a maximum column size // - FieldTypeInfo can be specified for sets or enumerations, as such: // ! aTable.SetFieldType('Sample',sftEnumerate,TypeInfo(TEnumSample)); // ! aTable.SetFieldType('Samples',sftSet,TypeInfo(TSetSamples)); procedure SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType; FieldTypeInfo: pointer=nil; FieldSize: integer=-1); overload; /// increase a particular Field Length Mean value // - to be used to customize the field appareance (e.g. for adding of left // checkbox for Marked[] fields) procedure FieldLengthMeanIncrease(aField, aIncrease: integer); /// copy the parameters of a TSQLTable into this instance // - the fResults remain in the source TSQLTable: source TSQLTable has not to ................................................................................ ContentTypeInfo := FieldTypeInfo; ContentType := NullableTypeToSQLFieldType(FieldTypeInfo); if ContentType=sftUnknown then ContentType := sftNullable; end; {$endif} end; TableIndex := FieldTableIndex; end; end; procedure TSQLTable.SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType; FieldTypeInfo: pointer=nil; FieldSize: integer=-1); begin SetFieldType(FieldIndex(FieldName),FieldType,FieldTypeInfo,FieldSize); end; function TSQLTable.GetRowCount: integer; begin if self=nil then result := 0 else result := fRowCount; end; ................................................................................ procedure TSQLTable.InitFieldTypes; var f,i,len: integer; sft: TSQLFieldType; info: pointer; prop: TSQLPropInfo; size,tableindex: integer; U: PPUTF8Char; tlog: TTimeLog; begin if Assigned(fQueryColumnTypes) and (FieldCount<>length(fQueryColumnTypes)) then raise ESQLTableException.CreateUTF8('%.CreateWithColumnTypes() called with % '+ 'column types, whereas the result has % columns', [self,length(fQueryColumnTypes),FieldCount]); SetLength(fFieldType,FieldCount); for f := 0 to FieldCount-1 do begin prop := nil; info := nil; size := -1; tableindex := -1; // init fFieldType[] from fQueryTables/fQueryColumnTypes[] if Assigned(fQueryColumnTypes) then sft := fQueryColumnTypes[f] else if Assigned(QueryTables) then begin // retrieve column info from field name sft := FieldPropFromTables(fResults[f],prop,tableindex); if prop<>nil then begin if prop.InheritsFrom(TSQLPropInfoRTTI) then ................................................................................ size := prop.FieldWidth; end; end else sft := sftUnknown; if sft=sftUnknown then // not found in fQueryTables/fQueryColumnTypes[]: guess from content if IsRowID(fResults[f]) then sft := sftInteger else if f in fFieldParsedAsString then begin // the parser identified string values -> check if was sftDateTime sft := sftUTF8Text; U := @fResults[FieldCount+f]; for i := 1 to fRowCount do if U^=nil then // search for a non void column inc(U,FieldCount) else begin len := StrLen(U^); tlog := Iso8601ToTimeLogPUTF8Char(U^,len); if tlog<>0 then if (len in [8,10]) and (cardinal(tlog shr 26)-1800<300) then sft := sftDateTime else // e.g. YYYYMMDD date (Y=1800..2100) if len>=15 then sft := sftDateTime; // e.g. YYYYMMDDThhmmss date/time value break; end; end else begin U := @fResults[FieldCount+f]; for i := 1 to fRowCount do begin sft := UTF8ContentNumberType(U^); inc(U,FieldCount); if sft=sftUnknown then continue else // null -> search for a non void column if sft=sftInteger then // may be a floating point with no decimal if FieldTypeIntegerDetectionOnAllRows then continue else // we only checked the first field -> best guess... sft := sftCurrency; break; // found a non-integer content (e.g. sftFloat/sftUtf8Text) end; end; SetFieldType(f,sft,info,size,tableindex); end; end; function TSQLTable.FieldType(Field: integer): TSQLFieldType; begin if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin if not Assigned(fFieldType) then InitFieldTypes; result := fFieldType[Field].ContentType; end else result := sftUnknown; end; function TSQLTable.FieldType(Field: integer; out FieldTypeInfo: TSQLTableFieldType): TSQLFieldType; begin if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin if not Assigned(fFieldType) then InitFieldTypes; FieldTypeInfo := fFieldType[Field]; result := FieldTypeInfo.ContentType; end else result := sftUnknown; end; function TSQLTable.Get(Row, Field: integer): PUTF8Char; begin if (self=nil) or (fResults=nil) or (cardinal(Row)>cardinal(fRowCount)) or (cardinal(Field)>=cardinal(FieldCount)) then // cardinal() -> test <0 result := nil else ................................................................................ MoveFast(pointer(Trail)^,P[len^],length(Trail)); tmp.Done; end; procedure TSQLTable.GetJSONValues(JSON: TStream; Expand: boolean; RowFirst, RowLast, IDBinarySize: integer); var W: TJSONWriter; F,R: integer; U: PPUTF8Char; i64: Int64; directWrites: set of 0..255; begin W := TJSONWriter.Create(JSON,Expand,false); try if (self=nil) or (FieldCount<=0) or (fRowCount<=0) then begin W.CancelAllVoid; exit; end; ................................................................................ RowLast := fRowCount; if RowFirst<=0 then RowFirst := 1; // start reading after first Row (Row 0 = Field Names) // get col names and types if QueryTables<>nil then InitFieldTypes; SetLength(W.ColNames,FieldCount); FillCharFast(directWrites,(FieldCount shr 3)+1,0); for F := 0 to FieldCount-1 do begin W.ColNames[F] := fResults[F]; // first Row is field Names if not Assigned(OnExportValue) then if F=fFieldIndexID then begin include(directWrites,F); // RowID is a ftInt64 if IDBinarySize>0 then W.ColNames[F] := 'id'; // ajax-friendly end else if QueryTables<>nil then with fFieldType[F] do if SQLFieldTypeToDBField(ContentType,ContentTypeInfo) in [ftInt64,ftDouble,ftCurrency] then include(directWrites,F); end; W.AddColumns(RowLast-RowFirst+1); // write or init field names (see JSON Expand) if Expand then W.Add('['); // write rows data U := @fResults[FieldCount*RowFirst]; for R := RowFirst to RowLast do begin if Expand then W.Add('{'); for F := 0 to FieldCount-1 do begin if Expand then W.AddString(W.ColNames[F]); // '"'+ColNames[]+'":' if Assigned(OnExportValue) then W.AddString(OnExportValue(self,R,F,false)) else if (IDBinarySize>0) and (F=fFieldIndexID) then begin SetInt64(U^,i64); W.AddBinToHexDisplayQuoted(@i64,IDBinarySize); end else if U^=nil then W.AddShort('null') else // IsStringJSON() is fast and safe: no need to guess exact value type if (F in directWrites) or ((QueryTables=nil) and not IsStringJSON(U^)) then W.AddNoJSONEscape(U^,StrLen(U^)) else begin W.Add('"'); W.AddJSONEscape(U^,StrLen(U^)); W.Add('"'); end; W.Add(','); inc(U); // points to next value end; W.CancelLastComma; if Expand then begin W.Add('}',','); if R<>RowLast then W.AddCR; // make expanded json more human readable end else W.Add(','); end; W.EndJSONObject(1,0); // "RowCount": set by W.AddColumns(RowLast-RowFirst+1) finally W.Free; ................................................................................ // ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency, '','',' dt:type="i8"',' dt:type="float"',' dt:type="number" rs:dbtype="currency"', // ftDate, ftUTF8, ftBlob ' dt:type="dateTime"',' dt:type="string"',' dt:type="bin.hex"'); var W: TJSONWriter; f,r: integer; U: PPUTF8Char; fieldType: TSQLDBFieldTypeDynArray; begin W := TJSONWriter.Create(Dest,16384); try W.AddShort('<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" '+ 'xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" '+ 'xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">'); if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin // retrieve normalized field names and types if length(fFieldNames)<>fFieldCount then InitFieldNames; if not Assigned(fFieldType) then InitFieldTypes; SetLength(fieldType,FieldCount); for f := 0 to FieldCount-1 do with fFieldType[F] do fieldType[f] := SQLFieldTypeToDBField(ContentType,ContentTypeInfo); // check range if RowLast=0 then RowLast := fRowCount else if RowLast>fRowCount then RowLast := fRowCount; if RowFirst<=0 then RowFirst := 1; // start reading after first Row (Row 0 = Field Names) ................................................................................ W.AddShort('<s:Schema id="RowsetSchema"><s:ElementType name="row" content="eltOnly">'); for f := 0 to FieldCount-1 do begin W.AddShort('<s:AttributeType name="f'); W.Add(f); W.AddShort('" rs:name="'); W.AddString(fFieldNames[f]); W.Add('"'); W.AddString(FIELDTYPE_TOXML[fieldType[f]]); W.Add('/','>'); end; W.AddShort('</s:ElementType></s:Schema>'); // write rows data U := @fResults[FieldCount*RowFirst]; W.AddShort('<rs:data>'); for r := RowFirst to RowLast do begin W.AddShort('<z:row '); for f := 0 to FieldCount-1 do begin if U^<>nil then begin W.Add('f'); W.Add(f); W.Add('=','"'); case fieldType[f] of ftUnknown: if IsStringJSON(U^) then // no need to guess exact value type here W.AddXmlEscape(U^) else W.AddNoJSONEscape(U^,StrLen(U^)); ftInt64, ftDouble, ftCurrency: W.AddNoJSONEscape(U^,StrLen(U^)); ftDate, ftUTF8, ftBlob: W.AddXmlEscape(U^); end; W.Add('"',' '); end; inc(U); // points to next value end; W.Add('/','>'); end; W.AddShort('</rs:data>'); ................................................................................ '<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>'+ '<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/></manifest:manifest>'; var Zip: TZipWriteToStream; Dest: TRawByteStringStream; content: RawUTF8; W: TTextWriter; U: PPUTF8Char; R,F: integer; fieldType: TSQLDBFieldTypeDynArray; begin Dest := TRawByteStringStream.Create; try Zip := TZipWriteToStream.Create(Dest); try Zip.AddStored('mimetype',pointer(ODSmimetype),length(ODSmimetype)); Zip.AddDeflated('styles.xml',pointer(ODSstyles),length(ODSstyles)); ................................................................................ if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin if withColumnTypes then begin // retrieve normalized field names and types if length(fFieldNames)<>fFieldCount then InitFieldNames; if not Assigned(fFieldType) then InitFieldTypes; SetLength(fieldType,FieldCount); for f := 0 to FieldCount-1 do with fFieldType[F] do fieldType[f] := SQLFieldTypeToDBField(ContentType,ContentTypeInfo); end; // write column names W.AddShort('<table:table-row>'); U := pointer(fResults); for F := 1 to FieldCount do begin W.AddShort('<table:table-cell office:value-type="string"><text:p>'); W.AddXmlEscape(U^); W.AddShort('</text:p></table:table-cell>'); inc(U); // points to next value end; W.AddShort('</table:table-row>'); // and values for R := 1 to fRowCount do begin W.AddShort('<table:table-row>'); if withColumnTypes then begin for F := 0 to FieldCount-1 do begin W.AddShort('<table:table-cell office:value-type="'); case fieldType[F] of ftInt64, ftDouble, ftCurrency: begin W.AddShort('float" office:value="'); W.AddXmlEscape(U^); end; ftDate: begin W.AddShort('date" office:date-value="'); W.AddXmlEscape(U^); end; else //ftUnknown, ftNull, ftUTF8, ftBlob: W.AddShort('string'); end; W.AddShort('"><text:p>'); if fieldType[F] in [ftUnknown, ftUTF8, ftBlob] then W.AddXmlEscape(U^); W.AddShort('</text:p></table:table-cell>'); inc(U); // points to next value end; end else for F := 0 to FieldCount-1 do begin W.AddShort('<table:table-cell office:value-type="string"><text:p>'); W.AddXmlEscape(U^); W.AddShort('</text:p></table:table-cell>'); inc(U); end; W.AddShort('</table:table-row>'); end; ................................................................................ StartRow, FieldIndex: integer; Client: TObject; Lang: TSynSoundExPronunciation; UnicodeComparison: boolean): integer; var U: PPUTF8Char; Kind: TSQLFieldType; Search: PAnsiChar; UpperUnicode: RawUnicode; UpperUnicodeLen: integer; info: TSQLTableFieldType; Val64: Int64; ValTimeLog: TTimelogBits absolute Val64; i,err: integer; EnumValue: RawUTF8; s: string; P: PShortString; EnumValues: set of 0..63; ................................................................................ function TSQLTable.GetVariant(Row, Field: integer): Variant; begin GetVariant(Row,Field,result); end; procedure TSQLTable.GetVariant(Row,Field: integer; var result: variant); var aType: TSQLFieldType; info: TSQLTableFieldType; begin if Row=0 then // Field Name RawUTF8ToVariant(GetU(0,Field),result) else begin aType := FieldType(Field,info); ValueVarToVariant(Get(Row,Field),aType,TVarData(result),true,info.ContentTypeInfo); end; end; ................................................................................ GetVariant(r,v,Result); end; {$endif NOVARIANTS} function TSQLTable.ExpandAsString(Row, Field: integer; Client: TObject; out Text: string; const CustomFormat: string): TSQLFieldType; var info: TSQLTableFieldType; err: integer; Value: Int64; ValueTimeLog: TTimeLogBits absolute Value; ValueDateTime: TDateTime; Ref: RecordRef absolute Value; label IsDateTime; begin // Text was already forced to '' because was defined as "out" parameter ................................................................................ sftEnumerate, sftSet, sftRecord, sftID, sftTID, sftRecordVersion, sftSessionUserID, sftTimeLog, sftModTime, sftCreateTime, sftUnixTime, sftUnixMSTime: begin Value := GetInt64(Get(Row,Field),err); if err<>0 then // not an integer -> to be displayed as sftUTF8Text result := sftUTF8Text else case result of sftEnumerate: begin Text := PEnumType(info.ContentTypeInfo)^.GetCaption(Value); exit; end; sftTimeLog, sftModTime, sftCreateTime: goto IsDateTime; sftUnixTime: begin ValueTimeLog.FromUnixTime(Value); goto IsDateTime; end; sftUnixMSTime: ................................................................................ Update(Row,Field,U); end; {$endif NOVARIANTS} procedure TSQLTableWritable.Join(From: TSQLTable; const FromKeyField, KeyField: RawUTF8); var fk,dk,f,i,k,ndx: integer; n,fn: RawUTF8; info: TSQLTableFieldType; new: TIntegerDynArray; begin dk := FieldIndexExisting(KeyField); SetLength(new,FieldCount); fk := From.FieldIndexExisting(FromKeyField); From.SortFields(fk); // faster merge with O(log(n)) binary search for f := 0 to From.FieldCount-1 do // add From fields (excluding FromKeyField) ................................................................................ if From.FieldType(f,info)=sftUnknown then // set TSQLTableFieldType i := AddField(fn) else if info.TableIndex>=0 then i := AddField(fn,From.QueryTables[info.TableIndex],n) else begin i := AddField(fn); if i>=length(fFieldType) then SetLength(fFieldType,i+1); fFieldType[i] := info; end; new[f] := i; end; ndx := FieldCount; for i := 1 to RowCount do begin // merge data k := From.SearchFieldSorted(fResults[ndx+dk],fk); if k>0 then begin ................................................................................ if IdemPropName(BlobFields[i].PropInfo^.Name,PropName,PropNameLen) then begin result := BlobFields[i].PropInfo; exit; end; result := nil; end; const DBTOFIELDTYPE: array[TSQLDBFieldType] of TSQLFieldType = (sftUnknown, sftUnknown,sftInteger,sftFloat,sftCurrency,sftDateTime,sftUTF8Text,sftBlob); function TSQLRecordProperties.SQLFieldTypeToSQL(FieldIndex: integer): RawUTF8; const /// simple wrapper from each SQL used type into SQLite3 field datatype // - set to '' for fields with no column created in the database DEFAULT_SQLFIELDTYPETOSQL: array[TSQLFieldType] of RawUTF8 = ('', // sftUnknown ' TEXT COLLATE NOCASE, ', // sftAnsiText |
> > > > | > > > | > > > > > > > > > > > > > > > | > | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | > > > | | | | > > < > | < | | | < < | < < < < < < | | | | | | | | > | | | > > > > | < < < < < | < < < < < < < < | < | < < < < < | | | | | | | | | | | | | > | | | | | < < < < |
8146 8147 8148 8149 8150 8151 8152 8153 8154 8155 8156 8157 8158 8159 8160 8161 8162 8163 8164 8165 8166 8167 8168 8169 8170 .... 8571 8572 8573 8574 8575 8576 8577 8578 8579 8580 8581 8582 8583 8584 8585 .... 8622 8623 8624 8625 8626 8627 8628 8629 8630 8631 8632 8633 8634 8635 8636 8637 ..... 25292 25293 25294 25295 25296 25297 25298 25299 25300 25301 25302 25303 25304 25305 25306 25307 25308 25309 25310 25311 25312 25313 25314 25315 25316 25317 25318 25319 25320 25321 25322 25323 25324 25325 25326 25327 25328 ..... 25330 25331 25332 25333 25334 25335 25336 25337 25338 25339 25340 25341 25342 25343 25344 25345 25346 25347 25348 25349 25350 25351 25352 25353 25354 25355 25356 25357 ..... 25359 25360 25361 25362 25363 25364 25365 25366 25367 25368 25369 25370 25371 25372 25373 25374 25375 25376 25377 25378 25379 25380 25381 25382 25383 25384 25385 25386 25387 25388 25389 25390 25391 25392 25393 25394 25395 25396 25397 25398 25399 25400 25401 25402 25403 25404 25405 25406 25407 25408 25409 25410 25411 25412 25413 25414 25415 25416 25417 25418 25419 25420 25421 25422 25423 25424 25425 25426 25427 25428 25429 25430 25431 25432 25433 ..... 25798 25799 25800 25801 25802 25803 25804 25805 25806 25807 25808 25809 25810 25811 25812 25813 25814 25815 ..... 25820 25821 25822 25823 25824 25825 25826 25827 25828 25829 25830 25831 25832 25833 25834 25835 25836 25837 25838 25839 25840 25841 25842 25843 25844 25845 25846 25847 25848 25849 25850 25851 25852 25853 25854 25855 25856 25857 25858 25859 25860 25861 25862 25863 25864 25865 25866 25867 25868 25869 25870 25871 25872 25873 25874 25875 25876 25877 ..... 25948 25949 25950 25951 25952 25953 25954 25955 25956 25957 25958 25959 25960 25961 25962 25963 25964 25965 25966 25967 25968 25969 25970 25971 25972 25973 ..... 25975 25976 25977 25978 25979 25980 25981 25982 25983 25984 25985 25986 25987 25988 25989 25990 25991 25992 25993 25994 25995 25996 25997 25998 25999 26000 26001 26002 26003 ..... 26037 26038 26039 26040 26041 26042 26043 26044 26045 26046 26047 26048 26049 26050 26051 ..... 26061 26062 26063 26064 26065 26066 26067 26068 26069 26070 26071 26072 26073 26074 26075 26076 26077 26078 26079 26080 26081 26082 26083 26084 26085 26086 26087 26088 26089 26090 26091 26092 26093 26094 26095 26096 26097 26098 26099 26100 26101 26102 26103 26104 26105 26106 26107 26108 26109 26110 26111 ..... 27227 27228 27229 27230 27231 27232 27233 27234 27235 27236 27237 27238 27239 27240 27241 ..... 27422 27423 27424 27425 27426 27427 27428 27429 27430 27431 27432 27433 27434 27435 27436 ..... 27448 27449 27450 27451 27452 27453 27454 27455 27456 27457 27458 27459 27460 27461 27462 ..... 27513 27514 27515 27516 27517 27518 27519 27520 27521 27522 27523 27524 27525 27526 27527 27528 27529 27530 27531 ..... 28587 28588 28589 28590 28591 28592 28593 28594 28595 28596 28597 28598 28599 28600 28601 ..... 28611 28612 28613 28614 28615 28616 28617 28618 28619 28620 28621 28622 28623 28624 28625 ..... 50510 50511 50512 50513 50514 50515 50516 50517 50518 50519 50520 50521 50522 50523 |
TOnSQLTableGetValue = function(Sender: TSQLTable; Row, Field: integer; HumanFriendly: boolean): RawJSON of object; /// store TSQLFieldType and RTTI for a given TSQLTable field TSQLTableFieldType = record /// the field kind, as in JSON (match TSQLPropInfo.SQLFieldTypeStored) ContentType: TSQLFieldType; /// how this field could be stored in a database // - equals ftUnknown if InitFields guessed the field type ContentDB: TSQLDBFieldType; /// the field size in bytes; -1 means not computed yet ContentSize: integer; /// used for sftEnumerate, sftSet and sftBlobDynArray fields ContentTypeInfo: pointer; /// the corresponding index in fQueryTables[] TableIndex: integer; end; PSQLTableFieldType = ^TSQLTableFieldType; /// wrapper to an ORM result table, staticaly stored as UTF-8 text // - contain all result in memory, until destroyed // - first row contains the field names // - following rows contains the data itself // - GetString() can be used in a TDrawString // - will be implemented as TSQLTableJSON for remote access through optimized ................................................................................ /// guess the field type from first non null data row // - if QueryTables[] are set, exact field type and (enumerate) TypeInfo() is // retrieved from the Delphi RTTI; otherwise, get from the cells content // - return sftUnknown is all data fields are null // - sftBlob is returned if the field is encoded as SQLite3 BLOB literals // (X'53514C697465' e.g.) // - since TSQLTable data is PUTF8Char, string type is sftUTF8Text only function FieldType(Field: integer; out FieldTypeInfo: PSQLTableFieldType): TSQLFieldType; overload; /// get the appropriate Sort comparaison function for a field, // nil if not available (bad field index or field is blob) // - field type is guessed from first data row function SortCompare(Field: integer): TUTF8Compare; /// get the mean of characters length of all fields // - the character length is for the first line of text only (stop counting // at every newline character, i.e. #10 or #13 char) ................................................................................ // - you can define a specific type for a given column, and optionally // a maximum column size // - FieldTypeInfo can be specified for sets or enumerations, as such: // ! aTable.SetFieldType('Sample',sftEnumerate,TypeInfo(TEnumSample)); // ! aTable.SetFieldType('Samples',sftSet,TypeInfo(TSetSamples)); procedure SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType; FieldTypeInfo: pointer=nil; FieldSize: integer=-1); overload; /// set the exact type of all fields, from the DB-like information procedure SetFieldTypes(const DBTypes: TSQLDBFieldTypeDynArray); /// increase a particular Field Length Mean value // - to be used to customize the field appareance (e.g. for adding of left // checkbox for Marked[] fields) procedure FieldLengthMeanIncrease(aField, aIncrease: integer); /// copy the parameters of a TSQLTable into this instance // - the fResults remain in the source TSQLTable: source TSQLTable has not to ................................................................................ ContentTypeInfo := FieldTypeInfo; ContentType := NullableTypeToSQLFieldType(FieldTypeInfo); if ContentType=sftUnknown then ContentType := sftNullable; end; {$endif} end; ContentDB := SQLFieldTypeToDBField(ContentType,ContentTypeInfo); TableIndex := FieldTableIndex; end; end; procedure TSQLTable.SetFieldType(const FieldName: RawUTF8; FieldType: TSQLFieldType; FieldTypeInfo: pointer; FieldSize: integer); begin SetFieldType(FieldIndex(FieldName),FieldType,FieldTypeInfo,FieldSize); end; const DBTOFIELDTYPE: array[TSQLDBFieldType] of TSQLFieldType = (sftUnknown, sftUnknown,sftInteger,sftFloat,sftCurrency,sftDateTime,sftUTF8Text,sftBlob); procedure TSQLTable.SetFieldTypes(const DBTypes: TSQLDBFieldTypeDynArray); var f: integer; begin if length(DBTypes)<>FieldCount then raise ESQLTableException.CreateUTF8('%.SetFieldTypes(DBTypes?)',[self]); for f := 0 to FieldCount-1 do SetFieldType(f,DBTOFIELDTYPE[DBTypes[f]]); end; function TSQLTable.GetRowCount: integer; begin if self=nil then result := 0 else result := fRowCount; end; ................................................................................ procedure TSQLTable.InitFieldTypes; var f,i,len: integer; sft: TSQLFieldType; info: pointer; prop: TSQLPropInfo; size,tableindex: integer; U: PPUTF8Char; guessed: boolean; tlog: TTimeLog; begin if Assigned(fQueryColumnTypes) and (FieldCount<>length(fQueryColumnTypes)) then raise ESQLTableException.CreateUTF8('%.CreateWithColumnTypes() called with % '+ 'column types, whereas the result has % columns', [self,length(fQueryColumnTypes),FieldCount]); SetLength(fFieldType,FieldCount); for f := 0 to FieldCount-1 do begin prop := nil; info := nil; size := -1; tableindex := -1; guessed := false; // init fFieldType[] from fQueryTables/fQueryColumnTypes[] if Assigned(fQueryColumnTypes) then sft := fQueryColumnTypes[f] else if Assigned(QueryTables) then begin // retrieve column info from field name sft := FieldPropFromTables(fResults[f],prop,tableindex); if prop<>nil then begin if prop.InheritsFrom(TSQLPropInfoRTTI) then ................................................................................ size := prop.FieldWidth; end; end else sft := sftUnknown; if sft=sftUnknown then // not found in fQueryTables/fQueryColumnTypes[]: guess from content if IsRowID(fResults[f]) then sft := sftInteger else begin guessed := true; if f in fFieldParsedAsString then begin // the parser identified string values -> check if was sftDateTime sft := sftUTF8Text; U := @fResults[FieldCount+f]; for i := 1 to fRowCount do if U^=nil then // search for a non void column inc(U,FieldCount) else begin len := StrLen(U^); tlog := Iso8601ToTimeLogPUTF8Char(U^,len); if tlog<>0 then if (len in [8,10]) and (cardinal(tlog shr 26)-1800<300) then sft := sftDateTime else // e.g. YYYYMMDD date (Y=1800..2100) if len>=15 then sft := sftDateTime; // e.g. YYYYMMDDThhmmss date/time value break; end; end else begin U := @fResults[FieldCount+f]; for i := 1 to fRowCount do begin sft := UTF8ContentNumberType(U^); inc(U,FieldCount); if sft=sftUnknown then continue else // null -> search for a non void column if sft=sftInteger then // may be a floating point with no decimal if FieldTypeIntegerDetectionOnAllRows then continue else // we only checked the first field -> best guess... sft := sftCurrency; break; // found a non-integer content (e.g. sftFloat/sftUtf8Text) end; end; end; SetFieldType(f,sft,info,size,tableindex); if guessed then fFieldType[f].ContentDB := ftUnknown; // may fail on some later row end; end; function TSQLTable.FieldType(Field: integer): TSQLFieldType; begin if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin if not Assigned(fFieldType) then InitFieldTypes; result := fFieldType[Field].ContentType; end else result := sftUnknown; end; function TSQLTable.FieldType(Field: integer; out FieldTypeInfo: PSQLTableFieldType): TSQLFieldType; begin if (self<>nil) and (cardinal(Field)<cardinal(FieldCount)) then begin if not Assigned(fFieldType) then InitFieldTypes; FieldTypeInfo := @fFieldType[Field]; result := FieldTypeInfo^.ContentType; end else begin FieldTypeInfo := nil; result := sftUnknown; end; end; function TSQLTable.Get(Row, Field: integer): PUTF8Char; begin if (self=nil) or (fResults=nil) or (cardinal(Row)>cardinal(fRowCount)) or (cardinal(Field)>=cardinal(FieldCount)) then // cardinal() -> test <0 result := nil else ................................................................................ MoveFast(pointer(Trail)^,P[len^],length(Trail)); tmp.Done; end; procedure TSQLTable.GetJSONValues(JSON: TStream; Expand: boolean; RowFirst, RowLast, IDBinarySize: integer); var W: TJSONWriter; U: PPUTF8Char; f,r: integer; i64: Int64; label nostr,str; begin W := TJSONWriter.Create(JSON,Expand,false); try if (self=nil) or (FieldCount<=0) or (fRowCount<=0) then begin W.CancelAllVoid; exit; end; ................................................................................ RowLast := fRowCount; if RowFirst<=0 then RowFirst := 1; // start reading after first Row (Row 0 = Field Names) // get col names and types if QueryTables<>nil then InitFieldTypes; SetLength(W.ColNames,FieldCount); for f := 0 to FieldCount-1 do begin W.ColNames[f] := fResults[f]; // first Row is field Names if not Assigned(OnExportValue) then if (f=fFieldIndexID) and (IDBinarySize>0) then W.ColNames[f] := 'id'; // ajax-friendly end; W.AddColumns(RowLast-RowFirst+1); // write or init field names (see JSON Expand) if Expand then W.Add('['); // write rows data U := @fResults[FieldCount*RowFirst]; for r := RowFirst to RowLast do begin if Expand then W.Add('{'); for f := 0 to FieldCount-1 do begin if Expand then W.AddString(W.ColNames[f]); // '"'+ColNames[]+'":' if Assigned(OnExportValue) then W.AddString(OnExportValue(self,r,f,false)) else if (IDBinarySize>0) and (f=fFieldIndexID) then begin SetInt64(U^,i64); W.AddBinToHexDisplayQuoted(@i64,IDBinarySize); end else if U^=nil then W.AddShort('null') else case fFieldType[f].ContentDB of ftInt64,ftDouble,ftCurrency: nostr: W.AddNoJSONEscape(U^,StrLen(U^)); ftDate,ftUTF8,ftBlob: begin str: W.Add('"'); W.AddJSONEscape(U^,StrLen(U^)); W.Add('"'); end; else if IsStringJSON(U^) then // fast and safe enough goto str else goto nostr; end; W.Add(','); inc(U); // points to next value end; W.CancelLastComma; if Expand then begin W.Add('}',','); if r<>RowLast then W.AddCR; // make expanded json more human readable end else W.Add(','); end; W.EndJSONObject(1,0); // "RowCount": set by W.AddColumns(RowLast-RowFirst+1) finally W.Free; ................................................................................ // ftUnknown, ftNull, ftInt64, ftDouble, ftCurrency, '','',' dt:type="i8"',' dt:type="float"',' dt:type="number" rs:dbtype="currency"', // ftDate, ftUTF8, ftBlob ' dt:type="dateTime"',' dt:type="string"',' dt:type="bin.hex"'); var W: TJSONWriter; f,r: integer; U: PPUTF8Char; begin W := TJSONWriter.Create(Dest,16384); try W.AddShort('<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882" '+ 'xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882" '+ 'xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">'); if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin // retrieve normalized field names and types if length(fFieldNames)<>fFieldCount then InitFieldNames; if not Assigned(fFieldType) then InitFieldTypes; // check range if RowLast=0 then RowLast := fRowCount else if RowLast>fRowCount then RowLast := fRowCount; if RowFirst<=0 then RowFirst := 1; // start reading after first Row (Row 0 = Field Names) ................................................................................ W.AddShort('<s:Schema id="RowsetSchema"><s:ElementType name="row" content="eltOnly">'); for f := 0 to FieldCount-1 do begin W.AddShort('<s:AttributeType name="f'); W.Add(f); W.AddShort('" rs:name="'); W.AddString(fFieldNames[f]); W.Add('"'); W.AddString(FIELDTYPE_TOXML[fFieldType[f].ContentDB]); W.Add('/','>'); end; W.AddShort('</s:ElementType></s:Schema>'); // write rows data U := @fResults[FieldCount*RowFirst]; W.AddShort('<rs:data>'); for r := RowFirst to RowLast do begin W.AddShort('<z:row '); for f := 0 to FieldCount-1 do begin if U^<>nil then begin W.Add('f'); W.Add(f); W.Add('=','"'); W.AddXmlEscape(U^); W.Add('"',' '); end; inc(U); // points to next value end; W.Add('/','>'); end; W.AddShort('</rs:data>'); ................................................................................ '<manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="settings.xml" manifest:media-type="text/xml"/>'+ '<manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/><manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/></manifest:manifest>'; var Zip: TZipWriteToStream; Dest: TRawByteStringStream; content: RawUTF8; W: TTextWriter; U: PPUTF8Char; r,f: integer; begin Dest := TRawByteStringStream.Create; try Zip := TZipWriteToStream.Create(Dest); try Zip.AddStored('mimetype',pointer(ODSmimetype),length(ODSmimetype)); Zip.AddDeflated('styles.xml',pointer(ODSstyles),length(ODSstyles)); ................................................................................ if (self<>nil) and ((FieldCount>0) or (fRowCount>0)) then begin if withColumnTypes then begin // retrieve normalized field names and types if length(fFieldNames)<>fFieldCount then InitFieldNames; if not Assigned(fFieldType) then InitFieldTypes; end; // write column names W.AddShort('<table:table-row>'); U := pointer(fResults); for f := 1 to FieldCount do begin W.AddShort('<table:table-cell office:value-type="string"><text:p>'); W.AddXmlEscape(U^); W.AddShort('</text:p></table:table-cell>'); inc(U); // points to next value end; W.AddShort('</table:table-row>'); // and values for r := 1 to fRowCount do begin W.AddShort('<table:table-row>'); if withColumnTypes then begin for f := 0 to FieldCount-1 do begin W.AddShort('<table:table-cell office:value-type="'); case fFieldType[f].ContentDB of ftInt64,ftDouble,ftCurrency: begin W.AddShort('float" office:value="'); W.AddXmlEscape(U^); end; ftDate: begin W.AddShort('date" office:date-value="'); W.AddXmlEscape(U^); end; else //ftUnknown, ftNull, ftUTF8, ftBlob: W.AddShort('string'); end; W.AddShort('"><text:p>'); if fFieldType[f].ContentDB in [ftUnknown, ftUTF8, ftBlob] then W.AddXmlEscape(U^); W.AddShort('</text:p></table:table-cell>'); inc(U); // points to next value end; end else for f := 0 to FieldCount-1 do begin W.AddShort('<table:table-cell office:value-type="string"><text:p>'); W.AddXmlEscape(U^); W.AddShort('</text:p></table:table-cell>'); inc(U); end; W.AddShort('</table:table-row>'); end; ................................................................................ StartRow, FieldIndex: integer; Client: TObject; Lang: TSynSoundExPronunciation; UnicodeComparison: boolean): integer; var U: PPUTF8Char; Kind: TSQLFieldType; Search: PAnsiChar; UpperUnicode: RawUnicode; UpperUnicodeLen: integer; info: PSQLTableFieldType; Val64: Int64; ValTimeLog: TTimelogBits absolute Val64; i,err: integer; EnumValue: RawUTF8; s: string; P: PShortString; EnumValues: set of 0..63; ................................................................................ function TSQLTable.GetVariant(Row, Field: integer): Variant; begin GetVariant(Row,Field,result); end; procedure TSQLTable.GetVariant(Row,Field: integer; var result: variant); var aType: TSQLFieldType; info: PSQLTableFieldType; begin if Row=0 then // Field Name RawUTF8ToVariant(GetU(0,Field),result) else begin aType := FieldType(Field,info); ValueVarToVariant(Get(Row,Field),aType,TVarData(result),true,info.ContentTypeInfo); end; end; ................................................................................ GetVariant(r,v,Result); end; {$endif NOVARIANTS} function TSQLTable.ExpandAsString(Row, Field: integer; Client: TObject; out Text: string; const CustomFormat: string): TSQLFieldType; var info: PSQLTableFieldType; err: integer; Value: Int64; ValueTimeLog: TTimeLogBits absolute Value; ValueDateTime: TDateTime; Ref: RecordRef absolute Value; label IsDateTime; begin // Text was already forced to '' because was defined as "out" parameter ................................................................................ sftEnumerate, sftSet, sftRecord, sftID, sftTID, sftRecordVersion, sftSessionUserID, sftTimeLog, sftModTime, sftCreateTime, sftUnixTime, sftUnixMSTime: begin Value := GetInt64(Get(Row,Field),err); if err<>0 then // not an integer -> to be displayed as sftUTF8Text result := sftUTF8Text else case result of sftEnumerate: if info.ContentTypeInfo<>nil then begin Text := PEnumType(info.ContentTypeInfo)^.GetCaption(Value); exit; end; sftTimeLog, sftModTime, sftCreateTime: goto IsDateTime; sftUnixTime: begin ValueTimeLog.FromUnixTime(Value); goto IsDateTime; end; sftUnixMSTime: ................................................................................ Update(Row,Field,U); end; {$endif NOVARIANTS} procedure TSQLTableWritable.Join(From: TSQLTable; const FromKeyField, KeyField: RawUTF8); var fk,dk,f,i,k,ndx: integer; n,fn: RawUTF8; info: PSQLTableFieldType; new: TIntegerDynArray; begin dk := FieldIndexExisting(KeyField); SetLength(new,FieldCount); fk := From.FieldIndexExisting(FromKeyField); From.SortFields(fk); // faster merge with O(log(n)) binary search for f := 0 to From.FieldCount-1 do // add From fields (excluding FromKeyField) ................................................................................ if From.FieldType(f,info)=sftUnknown then // set TSQLTableFieldType i := AddField(fn) else if info.TableIndex>=0 then i := AddField(fn,From.QueryTables[info.TableIndex],n) else begin i := AddField(fn); if i>=length(fFieldType) then SetLength(fFieldType,i+1); fFieldType[i] := info^; end; new[f] := i; end; ndx := FieldCount; for i := 1 to RowCount do begin // merge data k := From.SearchFieldSorted(fResults[ndx+dk],fk); if k>0 then begin ................................................................................ if IdemPropName(BlobFields[i].PropInfo^.Name,PropName,PropNameLen) then begin result := BlobFields[i].PropInfo; exit; end; result := nil; end; function TSQLRecordProperties.SQLFieldTypeToSQL(FieldIndex: integer): RawUTF8; const /// simple wrapper from each SQL used type into SQLite3 field datatype // - set to '' for fields with no column created in the database DEFAULT_SQLFIELDTYPETOSQL: array[TSQLFieldType] of RawUTF8 = ('', // sftUnknown ' TEXT COLLATE NOCASE, ', // sftAnsiText |
Changes to SynopseCommit.inc.
1 |
'1.18.4163'
|
| |
1 |
'1.18.4164'
|