O Help do Delphi 7, cita dois tipos de Variants: Variant arrays e OleVariant.
Abaixo extraímos da unit Variants as principais funções para trabalhar com tipos Variants, ou seja, as funções abaixo servem para trabalhar com tipos variants que armazenam tipos primitivos de dados, que armazenam dados em Variant Arrays (que é um tipo especial de array estático criado com chamadas as funções VarArrayCreate ou VarArrayOf) ou dados armazenados em OleVariant.
function VarType(const V: Variant): TVarType;
function VarAsType(const V: Variant; AVarType: TVarType): Variant;
function VarIsType(const V: Variant; AVarType: TVarType): Boolean; overload;
function VarIsType(const V: Variant; const AVarTypes: array of TVarType): Boolean; overload;
function VarIsByRef(const V: Variant): Boolean;
function VarIsEmpty(const V: Variant): Boolean;
procedure VarCheckEmpty(const V: Variant);
function VarIsNull(const V: Variant): Boolean;
function VarIsClear(const V: Variant): Boolean;
function VarIsCustom(const V: Variant): Boolean;
function VarIsOrdinal(const V: Variant): Boolean;
function VarIsFloat(const V: Variant): Boolean;
function VarIsNumeric(const V: Variant): Boolean;
function VarIsStr(const V: Variant): Boolean;
function VarToStr(const V: Variant): string;
function VarToStrDef(const V: Variant; const ADefault: string): string;
function VarToWideStr(const V: Variant): WideString;
function VarToWideStrDef(const V: Variant; const ADefault: WideString): WideString;
function VarToDateTime(const V: Variant): TDateTime;
function VarFromDateTime(const DateTime: TDateTime): Variant;
function VarInRange(const AValue, AMin, AMax: Variant): Boolean;
function VarEnsureRange(const AValue, AMin, AMax: Variant): Variant;
function VarSameValue(const A, B: Variant): Boolean;
function VarCompareValue(const A, B: Variant): TVariantRelationship;
function VarIsEmptyParam(const V: Variant): Boolean;
function VarIsError(const V: Variant; out AResult: HRESULT): Boolean; overload;
function VarIsError(const V: Variant): Boolean; overload;
function VarAsError(AResult: HRESULT): Variant;
function VarSupports(const V: Variant; const IID: TGUID; out Intf): Boolean; overload;
function VarSupports(const V: Variant; const IID: TGUID): Boolean; overload;
procedure VarCopyNoInd(var Dest: Variant; const Source: Variant);
function VarIsArray(const A: Variant): Boolean; overload;
function VarIsArray(const A: Variant; AResolveByRef: Boolean): Boolean; overload;
function VarArrayCreate(const Bounds: array of Integer; AVarType: TVarType): Variant;
function VarArrayOf(const Values: array of Variant): Variant;
function VarArrayRef(const A: Variant): Variant;
function VarTypeIsValidArrayType(const AVarType: TVarType): Boolean;
function VarTypeIsValidElementType(const AVarType: TVarType): Boolean;
function VarArrayDimCount(const A: Variant): Integer;
function VarArrayLowBound(const A: Variant; Dim: Integer): Integer;
function VarArrayHighBound(const A: Variant; Dim: Integer): Integer;
function VarArrayLock(const A: Variant): Pointer;
procedure VarArrayUnlock(const A: Variant);
function VarArrayAsPSafeArray(const A: Variant): PVarArray;
function VarArrayGet(const A: Variant; const Indices: array of Integer): Variant;
procedure VarArrayPut(var A: Variant; const Value: Variant; const Indices: array of Integer);
procedure DynArrayToVariant(var V: Variant; const DynArray: Pointer; TypeInfo: Pointer);
procedure DynArrayFromVariant(var DynArray: Pointer; const V: Variant; TypeInfo: Pointer);
function Unassigned: Variant;
function Null: Variant;
Neste mini-artigo queremos falar especialmente sobre a utilidade da função VarType.
A função VarType retorna um código ou constante de tipo para o variant passado como parâmetro para a função VarType.
Veja a sintaxe:
function VarType(const V: Variant): TVarType;
E abaixo segue uma lista de contants que representam o código do tipo retornado
VarType | Descrição do tipo retornado |
varEmpty | The variant is Unassigned. |
varNull | The variant is Null. |
varSmallint | 16-bit signed integer (type Smallint in Delphi, short in C++ ). |
varInteger | 32-bit signed integer (type Integer in Delphi, int in C++). |
varSingle | Single-precision floating-point value (type Single in Delphi, float in C++). |
varDouble | Double-precision floating-point value (type double). |
varCurrency | Currency floating-point value (type Currency). |
varDate | Date and time value (type TDateTime). |
varOleStr | Reference to a dynamically allocated UNICODE string. |
varDispatch | Reference to an Automation object (an IDispatch interface pointer). |
varError | Operating system error code. |
varBoolean | 16-bit boolean (type WordBool). |
varVariant | A variant. |
varUnknown | Reference to an unknown object (an IInterface or IUnknown interface pointer). |
varShortInt | 8-bit signed integer (type ShortInt in Delphi or signed char in C++) |
varByte | A Byte |
varWord | unsigned 16-bit value (Word) |
varLongWord | unsigned 32-bit value (type LongWord in Delphi or unsigned long in C++) |
varInt64 | 64-bit signed integer (Int64 in Delphi or __int64 in C++) |
varStrArg | COM-compatible string. |
varString | Reference to a dynamically allocated string (not COM compatible). |
varAny | A CORBA Any value. |
E estas constantes estão definidas na Unit System;
const
varEmpty = $0000; { vt_empty 0 }
varNull = $0001; { vt_null 1 }
varSmallint = $0002; { vt_i2 2 }
varInteger = $0003; { vt_i4 3 }
varSingle = $0004; { vt_r4 4 }
varDouble = $0005; { vt_r8 5 }
varCurrency = $0006; { vt_cy 6 }
varDate = $0007; { vt_date 7 }
varOleStr = $0008; { vt_bstr 8 }
varDispatch = $0009; { vt_dispatch 9 }
varError = $000A; { vt_error 10 }
varBoolean = $000B; { vt_bool 11 }
varVariant = $000C; { vt_variant 12 }
varUnknown = $000D; { vt_unknown 13 }
varShortInt = $0010; { vt_i1 16 }
varByte = $0011; { vt_ui1 17 }
varWord = $0012; { vt_ui2 18 }
varLongWord = $0013; { vt_ui4 19 }
varInt64 = $0014; { vt_i8 20 }
varStrArg = $0048; { vt_clsid 72 }
varString = $0100; { Pascal string 256 } {not OLE compatible }
varAny = $0101; { Corba any 257 } {not OLE compatible }
Onde o tema pode ser útil?
Uma situação que enfrentamos foi a seguinte: em uma aplicação datasnap multicamadas com delphi 2010 (tradicional com uso de componentes) passamos parâmetros no evento BeforeGetRecords do TClientDataSet. Veja o código:
procedure TnbaseGrade.ClientDataSetBeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
var
Params: TParams;
begin
Params := TParams.Create();
Params.CreateParam(ftString, 'TABELA', ptInput).AsString := FTabela;
Params.CreateParam(ftString, 'OUTROSCAMPOSSELECT', ptInput).AsString := '';
Params.CreateParam(ftString, FTabela, ptInput).AsString := EditConsulta.Text;
OwnerData := PackageParams(Params, [ptInput]);
end;
E no lado servidor, recebemos os parâmetros no evento BeforeGetRecords do DataSetProvider, onde temos uma rotina que monta uma sql dinamicamente com estes parâmetros passados e seta a propriedade CommandText do SqlDataSet, no qual deixamos fixo em tempo de projeto um "select * from tabela". Nosso objetivo não é mostrar aqui a rotina, e sim, expôr um problema para o qual demoramos encontrar a solucão, pois achávamos que era um bug do delphi 2010. Veja o código:
procedure TsmCadastro.DspDisciplinaBeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
begin
DstDisciplina.CommandText := MontaSql(OwnerData);
end;
A instrução acima funciona perfeitamente em tempo de execução. Porém em tempo de projeto, as vezes, ativamos no lado cliente nosso TClientDataSet para testar se tudo ficou configurado corretamente. Porém. em tempo de projeto os parâmetros não são passados e aí acontecia erros do tipo: "Remote Error: List index out of Bounds(0)", pois dentro de nossa rotina MontaSql estávamos tentando acessar o params[0] que deveria ter sempre nossa tabela, o params[1] que deveria ter os campos do select e os demais índices de params[2] até params[n-1] em que deveriam estar as condições para o where.
Como tratar este problema?
Poderíamos passar a sql original mudando a assinatura de nossa MontaSql para:
DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText) onde também passaríamos a sql definida em tempo de projeto e dentro de nossa rotina MontaSql podíamos fazer um teste para verificar se params.Count é maior que zero.
Ex.
if params.Count > 0 then
begin
//processa params
end
else
Result := sqlOriginal; //isto manteria a instrução sql fixada em tempo de projeto "select * from tabela"
Mas gostaríamos de não chamar a rotina MontaSql se o OwnerData não tiver nenhum parâmetro.
Como resolver então?
Aí que entra as funções para operar sobre tipos variants.
Mudamos então o código do evento BeforeGetRecords do DataSetProvider para:
procedure TsmCadastro.DspDisciplinaBeforeGetRecords(Sender: TObject; var OwnerData: OleVariant);
begin
if VarType(OwnerData) <> varEmpty then //leia se VarType de (OwnerData) diferente de Unassigned então
DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText);
//ou esta solução que é mais direta
//if TVarData(OwnerData).VType <> varEmpty then
//DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText);
//ou esta solução
//if OwnerData <> varEmpty then
//DstDisciplina.CommandText := MontaSql(OwnerData, DstDisciplina.CommandText);
end;
Com isto, garantimos que em tempo de projeto, ao ativar o ClientDataSet no lado cliente, nossa rotina MontaSql não será invocada no lado servidor, e o SqlDataSet será executado com a instrução definida em tempo de projeto.
Espero poder ajudar outros programadores iniciantes em DataSnap, que certamente iriam colher este mesmo erro ao implementar este tipo de solução com querys dinâmicas, independente se passar parâmetros com Variants Arrays, com TParams ou via TClientDataSet especializados, sendo que neste último, se não testássemos dentro do MontaSql se o TClientDataSet é vazio, quando ele não tiver registro o erro seria: "Invalid data packet", ao tentar fazer esta atribuição: DstDisciplina.Data := AOwnerData;
Em resumo, se testar-mos sempre o OwnerData antes de chamar a linha de comando DstDisciplina.CommandText := MontaSql(OwnerData) estaremos impedindo o erro em tempo de projeto indepente se os parâmetros são passados com Variants Arrays, com TParams, com TClientDataSet ou outra solução. E teremos uma solução unificada para validação dos parâmetros.
Abraços e até a próxima