Browse Source

+ Centralized all parameter-parsing in TParams.ParseSQL (bug 4374)

git-svn-id: trunk@1656 -
joost 20 years ago
parent
commit
6d291bb336

+ 7 - 0
fcl/db/db.pp

@@ -32,6 +32,7 @@ const
   // whether it's true or false.
   // whether it's true or false.
   YesNoChars : Array[Boolean] of char = ('Y','N');
   YesNoChars : Array[Boolean] of char = ('Y','N');
 
 
+  SQLDelimiterCharacters = [';',',',' ','(',')',#13,#10,#9];
 
 
 type
 type
 
 
@@ -1547,9 +1548,13 @@ type
   { TParam }
   { TParam }
 
 
   TBlobData = string;
   TBlobData = string;
+  
+  TParamBinding = array of integer;
 
 
   TParamType = (ptUnknown, ptInput, ptOutput, ptInputOutput, ptResult);
   TParamType = (ptUnknown, ptInput, ptOutput, ptInputOutput, ptResult);
   TParamTypes = set of TParamType;
   TParamTypes = set of TParamType;
+  
+  TParamStyle = (psInterbase,psPostgreSQL);
 
 
   TParams = class;
   TParams = class;
 
 
@@ -1661,6 +1666,8 @@ type
     Function  IsEqual(Value: TParams): Boolean;
     Function  IsEqual(Value: TParams): Boolean;
     Function  ParamByName(const Value: string): TParam;
     Function  ParamByName(const Value: string): TParam;
     Function  ParseSQL(SQL: String; DoCreate: Boolean): String;
     Function  ParseSQL(SQL: String; DoCreate: Boolean): String;
+    Function  ParseSQL(SQL: String; DoCreate: Boolean; ParameterStyle : TParamStyle): String; overload;
+    Function  ParseSQL(SQL: String; DoCreate: Boolean; ParameterStyle : TParamStyle; var ParamBinding: TParambinding): String; overload;
     Procedure RemoveParam(Value: TParam);
     Procedure RemoveParam(Value: TParam);
     Property Dataset : TDataset Read GetDataset;
     Property Dataset : TDataset Read GetDataset;
     Property Items[Index: Integer] : TParam read GetItem write SetItem; default;
     Property Items[Index: Integer] : TParam read GetItem write SetItem; default;

+ 194 - 0
fcl/db/dsparams.inc

@@ -151,10 +151,204 @@ begin
 end;
 end;
 
 
 Function TParams.ParseSQL(SQL: String; DoCreate: Boolean): String;
 Function TParams.ParseSQL(SQL: String; DoCreate: Boolean): String;
+
+var pb : TParamBinding;
+
+begin
+  Result := ParseSQL(SQL,DoCreate,psInterbase, pb);
+end;
+
+Function TParams.ParseSQL(SQL: String; DoCreate: Boolean; ParameterStyle : TParamStyle): String;
+
+var pb : TParamBinding;
+
 begin
 begin
+  Result := ParseSQL(SQL,DoCreate,ParameterStyle,pb);
+end;
+
+
+Function TParams.ParseSQL(SQL: String; DoCreate: Boolean; ParameterStyle : TParamStyle; var ParamBinding: TParambinding): String;
+
+type
+  // used for ParamPart
+  TStringPart = record
+    Start,Stop:integer;
+  end;
+  
+const
+  ParamAllocStepSize = 8;
+  
+var
+  p,ParamNameStart,BufStart:PChar;
+  ParamName:string;
+  QuestionMarkParamCount,ParameterIndex,NewLength:integer;
+  ParamCount:integer; // actual number of parameters encountered so far;
+                      // always <= Length(ParamPart) = Length(Parambinding)
+                      // Parambinding will have length ParamCount in the end
+  ParamPart:array of TStringPart; // describe which parts of buf are parameters
+  NewQueryLength:integer;
+  NewQuery:string;
+  NewQueryIndex,BufIndex,CopyLen,i:integer;                     // Parambinding will have length ParamCount in the end
+
+begin
+  if DoCreate then Clear;
+  // Parse the SQL and build ParamBinding
+  ParamCount:=0;
+  NewQueryLength:=Length(SQL);
+  SetLength(ParamPart,ParamAllocStepSize);
+  SetLength(Parambinding,ParamAllocStepSize);
+  QuestionMarkParamCount:=0; // number of ? params found in query so far
+
+  p:=PChar(SQL);
+  BufStart:=p; // used to calculate ParamPart.Start values
+  repeat
+    case p^ of
+      '''': // single quote delimited string
+        begin
+          Inc(p);
+          while not (p^ in [#0, '''']) do
+          begin
+            if p^='\' then Inc(p,2) // make sure we handle \' and \\ correct
+            else Inc(p);
+          end;
+          if p^='''' then Inc(p); // skip final '
+        end;
+      '"':  // double quote delimited string
+        begin
+          Inc(p);
+          while not (p^ in [#0, '"']) do
+          begin
+            if p^='\'  then Inc(p,2) // make sure we handle \" and \\ correct
+            else Inc(p);
+          end;
+          if p^='"' then Inc(p); // skip final "
+        end;
+      '-': // possible start of -- comment
+        begin
+          Inc(p);
+          if p='-' then // -- comment
+          begin
+            repeat // skip until at end of line
+              Inc(p);
+            until p^ in [#10, #0];
+          end
+        end;
+      '/': // possible start of /* */ comment
+        begin
+          Inc(p);
+          if p^='*' then // /* */ comment
+          begin
+            repeat
+              Inc(p);
+              if p^='*' then // possible end of comment
+              begin
+                Inc(p);
+                if p^='/' then Break; // end of comment
+              end;
+            until p^=#0;
+            if p^='/' then Inc(p); // skip final /
+          end;
+        end;
+      ':','?': // parameter
+        begin
+          Inc(ParamCount);
+          if ParamCount>Length(ParamPart) then
+          begin
+            NewLength:=Length(ParamPart)+ParamAllocStepSize;
+            SetLength(ParamPart,NewLength);
+            SetLength(ParamBinding,NewLength);
+          end;
+
+          if p^=':' then
+          begin // find parameter name
+            Inc(p);
+            ParamNameStart:=p;
+            while not (p^ in (SQLDelimiterCharacters+[#0])) do
+              Inc(p);
+            ParamName:=Copy(ParamNameStart,1,p-ParamNameStart);
+          end
+          else
+          begin
+            Inc(p);
+            ParamNameStart:=p;
+            ParamName:='';
+          end;
+
+          // create Parameter and assign ParameterIndex
+          if DoCreate then
+            ParameterIndex := CreateParam(ftUnknown, ParamName, ptInput).Index
+          // else find ParameterIndex
+          else
+            begin
+              if ParamName<>'' then
+                ParameterIndex:=ParamByName(ParamName).Index
+              else
+              begin
+                ParameterIndex:=QuestionMarkParamCount;
+                Inc(QuestionMarkParamCount);
+              end;
+            end;
+
+          // store ParameterIndex in FParamIndex, ParamPart data
+          ParamBinding[ParamCount-1]:=ParameterIndex;
+          ParamPart[ParamCount-1].Start:=ParamNameStart-BufStart;
+          ParamPart[ParamCount-1].Stop:=p-BufStart+1;
+
+          // update NewQueryLength
+          Dec(NewQueryLength,p-ParamNameStart);
+        end;
+      #0:Break;
+    else
+      Inc(p);
+    end;
+  until false;
+  
+  SetLength(ParamPart,ParamCount);
+  SetLength(ParamBinding,ParamCount);
 
 
+  if ParamCount>0 then
+  begin
+    // replace :ParamName by ? (using ParamPart array and NewQueryLength)
+    if ParameterStyle = psPostgreSQL then
+      if paramcount < 10 then
+        inc(NewQueryLength,paramcount)
+      else
+        inc(NewQueryLength,(paramcount-9)*2+9);
+
+    SetLength(NewQuery,NewQueryLength);
+    NewQueryIndex:=1;
+    BufIndex:=1;
+    for i:=0 to High(ParamPart) do
+    begin
+      CopyLen:=ParamPart[i].Start-BufIndex;
+      Move(SQL[BufIndex],NewQuery[NewQueryIndex],CopyLen);
+      Inc(NewQueryIndex,CopyLen);
+      case ParameterStyle of
+        psInterbase : NewQuery[NewQueryIndex]:='?';
+        psPostgreSQL: begin
+                        ParamName := IntToStr(i+1);
+                        NewQuery[NewQueryIndex]:='$';
+                        Inc(NewQueryIndex);
+                        NewQuery[NewQueryIndex]:= paramname[1];
+                        if i>10 then
+                          begin
+                          Inc(NewQueryIndex);
+                          NewQuery[NewQueryIndex]:= paramname[2]
+                          end;
+                      end;
+      end;
+      Inc(NewQueryIndex);
+      BufIndex:=ParamPart[i].Stop;
+    end;
+    CopyLen:=Length(SQL)+1-BufIndex;
+    Move(SQL[BufIndex],NewQuery[NewQueryIndex],CopyLen);
+  end
+  else
+    NewQuery:=SQL;
+  Result := NewQuery;
 end;
 end;
 
 
+
 Procedure TParams.RemoveParam(Value: TParam);
 Procedure TParams.RemoveParam(Value: TParam);
 begin
 begin
    Value.Collection:=Nil;
    Value.Collection:=Nil;

+ 1 - 21
fcl/db/sqldb/interbase/ibconnection.pp

@@ -438,27 +438,7 @@ begin
     tr := aTransaction.Handle;
     tr := aTransaction.Handle;
     
     
     if assigned(AParams) and (AParams.count > 0) then
     if assigned(AParams) and (AParams.count > 0) then
-      begin
-      SetLength(ParamBinding,0);
-
-      i := posex(':',buf);
-      while i > 0 do
-        begin
-        inc(i);
-        p := @buf[i];
-        repeat
-        inc(p);
-        until (p^ in SQLDelimiterCharacters);
-
-        SetLength(ParamBinding,length(ParamBinding)+1);
-        parambinding[high(parambinding)] := AParams.ParamByName(copy(buf,i,p-@buf[i])).Index;
-
-        i := posex(':',buf,i);
-        end;
-
-        for x := 0 to AParams.count-1 do
-          buf := stringreplace(buf,':'+AParams[x].Name,'?',[rfReplaceAll,rfIgnoreCase]);
-      end;
+      buf := AParams.ParseSQL(buf,false,psInterbase,paramBinding);
 
 
     if isc_dsql_prepare(@Status, @tr, @Statement, 0, @Buf[1], Dialect, nil) <> 0 then
     if isc_dsql_prepare(@Status, @tr, @Statement, 0, @Buf[1], Dialect, nil) <> 0 then
       CheckError('PrepareStatement', Status);
       CheckError('PrepareStatement', Status);

+ 5 - 150
fcl/db/sqldb/odbc/odbcconn.pas

@@ -29,7 +29,7 @@ type
   protected
   protected
     FSTMTHandle:SQLHSTMT; // ODBC Statement Handle
     FSTMTHandle:SQLHSTMT; // ODBC Statement Handle
     FQuery:string;        // last prepared query, with :ParamName converted to ?
     FQuery:string;        // last prepared query, with :ParamName converted to ?
-    FParamIndex:array of integer; // maps the i-th parameter in the query to the TParams passed to PrepareStatement
+    FParamIndex:TParamBinding; // maps the i-th parameter in the query to the TParams passed to PrepareStatement
     FParamBuf:array of pointer; // buffers that can be used to bind the i-th parameter in the query
     FParamBuf:array of pointer; // buffers that can be used to bind the i-th parameter in the query
   public
   public
     constructor Create(Connection:TODBCConnection);
     constructor Create(Connection:TODBCConnection);
@@ -413,25 +413,8 @@ begin
 end;
 end;
 
 
 procedure TODBCConnection.PrepareStatement(cursor: TSQLCursor; ATransaction: TSQLTransaction; buf: string; AParams: TParams);
 procedure TODBCConnection.PrepareStatement(cursor: TSQLCursor; ATransaction: TSQLTransaction; buf: string; AParams: TParams);
-type
-  // used for ParamPart
-  TStringPart = record
-    Start,Stop:integer;
-  end;
-const
-  ParamAllocStepSize = 8;
 var
 var
   ODBCCursor:TODBCCursor;
   ODBCCursor:TODBCCursor;
-  p,ParamNameStart,BufStart:PChar;
-  ParamName:string;
-  QuestionMarkParamCount,ParameterIndex,NewLength:integer;
-  ParamCount:integer; // actual number of parameters encountered so far;
-                      // always <= Length(ParamPart) = Length(ODBCCursor.FParamIndex)
-                      // ODBCCursor.FParamIndex will have length ParamCount in the end
-  ParamPart:array of TStringPart; // describe which parts of buf are parameters
-  NewQueryLength:integer;
-  NewQuery:string;
-  NewQueryIndex,BufIndex,CopyLen,i:integer;
 begin
 begin
   ODBCCursor:=cursor as TODBCCursor;
   ODBCCursor:=cursor as TODBCCursor;
 
 
@@ -440,142 +423,14 @@ begin
   //       ODBCCursor.FParamIndex will map th i-th ? token in the (modified) query to an index for AParams
   //       ODBCCursor.FParamIndex will map th i-th ? token in the (modified) query to an index for AParams
 
 
   // Parse the SQL and build FParamIndex
   // Parse the SQL and build FParamIndex
-  ParamCount:=0;
-  NewQueryLength:=Length(buf);
-  SetLength(ParamPart,ParamAllocStepSize);
-  SetLength(ODBCCursor.FParamIndex,ParamAllocStepSize);
-  QuestionMarkParamCount:=0; // number of ? params found in query so far
-  p:=PChar(buf);
-  BufStart:=p; // used to calculate ParamPart.Start values
-  repeat
-    case p^ of
-      '''': // single quote delimited string (not obligatory in ODBC, but let's handle it anyway)
-        begin
-          Inc(p);
-          while not (p^ in [#0, '''']) do
-          begin
-            if p^='\' then Inc(p,2) // make sure we handle \' and \\ correct
-            else Inc(p);
-          end;
-          if p^='''' then Inc(p); // skip final '
-        end;
-      '"':  // double quote delimited string
-        begin
-          Inc(p);
-          while not (p^ in [#0, '"']) do
-          begin
-            if p^='\'  then Inc(p,2) // make sure we handle \" and \\ correct
-            else Inc(p);
-          end;
-          if p^='"' then Inc(p); // skip final "
-        end;
-      '-': // possible start of -- comment
-        begin
-          Inc(p);
-          if p='-' then // -- comment
-          begin
-            repeat // skip until at end of line
-              Inc(p);
-            until p^ in [#10, #0];
-          end
-        end;
-      '/': // possible start of /* */ comment
-        begin
-          Inc(p);
-          if p^='*' then // /* */ comment
-          begin
-            repeat
-              Inc(p);
-              if p^='*' then // possible end of comment
-              begin
-                Inc(p);
-                if p^='/' then Break; // end of comment
-              end;
-            until p^=#0;
-            if p^='/' then Inc(p); // skip final /
-          end;
-        end;
-      ':','?': // parameter
-        begin
-          Inc(ParamCount);
-          if ParamCount>Length(ParamPart) then
-          begin
-            NewLength:=Length(ParamPart)+ParamAllocStepSize;
-            SetLength(ParamPart,NewLength);
-            SetLength(ODBCCursor.FParamIndex,NewLength);
-          end;
-
-          if p^=':' then
-          begin // find parameter name
-            Inc(p);
-            ParamNameStart:=p;
-            while not (p^ in (SQLDelimiterCharacters+[#0])) do
-              Inc(p);
-            ParamName:=Copy(ParamNameStart,1,p-ParamNameStart);
-          end
-          else
-          begin
-            Inc(p);
-            ParamNameStart:=p;
-            ParamName:='';
-          end;
-
-          // find ParameterIndex
-          if ParamName<>'' then
-          begin
-            if AParams=nil then
-              raise EDataBaseError.CreateFmt('Found parameter marker with name %s in the query, but no actual parameters are given at all',[ParamName]);
-            ParameterIndex:=AParams.ParamByName(ParamName).Index // lookup parameter in AParams
-          end
-          else
-          begin
-            ParameterIndex:=QuestionMarkParamCount;
-            Inc(QuestionMarkParamCount);
-          end;
-
-          // store ParameterIndex in FParamIndex, ParamPart data
-          ODBCCursor.FParamIndex[ParamCount-1]:=ParameterIndex;
-          ParamPart[ParamCount-1].Start:=ParamNameStart-BufStart;
-          ParamPart[ParamCount-1].Stop:=p-BufStart+1;
-
-          // update NewQueryLength
-          Dec(NewQueryLength,p-ParamNameStart);
-        end;
-      #0:Break;
-    else
-      Inc(p);
-    end;
-  until false;
-
-  SetLength(ParamPart,ParamCount);
-  SetLength(ODBCCursor.FParamIndex,ParamCount);
-
-  if ParamCount>0 then
-  begin
-    // replace :ParamName by ? (using ParamPart array and NewQueryLength)
-    SetLength(NewQuery,NewQueryLength);
-    NewQueryIndex:=1;
-    BufIndex:=1;
-    for i:=0 to High(ParamPart) do
-    begin
-      CopyLen:=ParamPart[i].Start-BufIndex;
-      Move(buf[BufIndex],NewQuery[NewQueryIndex],CopyLen);
-      Inc(NewQueryIndex,CopyLen);
-      NewQuery[NewQueryIndex]:='?';
-      Inc(NewQueryIndex);
-      BufIndex:=ParamPart[i].Stop;
-    end;
-    CopyLen:=Length(Buf)+1-BufIndex;
-    Move(buf[BufIndex],NewQuery[NewQueryIndex],CopyLen);
-  end
-  else
-    NewQuery:=buf;
+  if assigned(AParams) and (AParams.count > 0) then
+    buf := AParams.ParseSQL(buf,false,psInterbase,ODBCCursor.FParamIndex);
 
 
   // prepare statement
   // prepare statement
-  SQLPrepare(ODBCCursor.FSTMTHandle, PChar(NewQuery), Length(NewQuery));
+  SQLPrepare(ODBCCursor.FSTMTHandle, PChar(Buf), Length(Buf));
   ODBCCheckResult(SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not prepare statement.');
   ODBCCheckResult(SQL_HANDLE_STMT, ODBCCursor.FSTMTHandle, 'Could not prepare statement.');
 
 
-  ODBCCursor.FQuery:=NewQuery;
+  ODBCCursor.FQuery:=Buf;
 end;
 end;
 
 
 procedure TODBCConnection.UnPrepareStatement(cursor: TSQLCursor);
 procedure TODBCConnection.UnPrepareStatement(cursor: TSQLCursor);

+ 1 - 3
fcl/db/sqldb/postgres/pqconnection.pp

@@ -408,11 +408,9 @@ begin
         begin
         begin
         s := s + '(';
         s := s + '(';
         for i := 0 to AParams.count-1 do
         for i := 0 to AParams.count-1 do
-          begin
           s := s + TypeStrings[AParams[i].DataType] + ',';
           s := s + TypeStrings[AParams[i].DataType] + ',';
-          buf := stringreplace(buf,':'+AParams[i].Name,'$'+inttostr(i+1),[rfReplaceAll,rfIgnoreCase]);
-          end;
         s[length(s)] := ')';
         s[length(s)] := ')';
+        buf := AParams.ParseSQL(buf,false,psPostgreSQL);
         end;
         end;
       s := s + ' as ' + buf;
       s := s + ' as ' + buf;
       res := pqexec(tr,pchar(s));
       res := pqexec(tr,pchar(s));

+ 3 - 18
fcl/db/sqldb/sqldb.pp

@@ -52,7 +52,6 @@ const
                   'create', 'get', 'put', 'execute',
                   'create', 'get', 'put', 'execute',
                   'start','commit','rollback', '?'
                   'start','commit','rollback', '?'
                  );
                  );
- SQLDelimiterCharacters = [';',',',' ','(',')',#13,#10,#9];
 
 
 
 
 { TSQLConnection }
 { TSQLConnection }
@@ -517,23 +516,9 @@ begin
   UnPrepare;
   UnPrepare;
   if (FSQL <> nil) then
   if (FSQL <> nil) then
     begin
     begin
-    if assigned(FParams) then FParams.Clear;
-    s := FSQL.Text;
-    i := posex(':',s);
-    while i > 0 do
-      begin
-      inc(i);
-      p := @s[i];
-      repeat
-      inc(p);
-      until (p^ in SQLDelimiterCharacters);
-      if not assigned(FParams) then FParams := TParams.create(self);
-      ParamName := copy(s,i,p-@s[i]);
-      if FParams.FindParam(ParamName) = nil then
-        FParams.CreateParam(ftUnknown, ParamName, ptInput);
-      i := posex(':',s,i);
-      end;
-    end
+    if not assigned(FParams) then FParams := TParams.create(self);
+    FParams.ParseSQL(FSQL.Text,True);
+    end;
 end;
 end;
 
 
 Procedure TSQLQuery.SetTransaction(Value : TDBTransaction);
 Procedure TSQLQuery.SetTransaction(Value : TDBTransaction);