Lazarus вставить результаты sql в сетку строк

У меня проблема со вставкой результатов sql в TStringGrid. У меня есть следующий код:

 var i:Integer;
 begin
   SqlQuery1.SQL.Text:= 'SELECT * FROM `users`'; 
   SqlQuery1.Open;
   MySql55Connection1.Open;
   i:= 0;
   while not SQLQUERY1.EOF do
   begin
     i:= i+1;
     StringGrid1.Cells[0,i]:= SqlQuery1.FieldByName('Username').AsString;
     StringGrid1.Cells[1,i]:= SqlQuery1.FieldByName('Password').AsString;
     StringGrid1.Cells[2,i]:= SqlQuery1.FieldByName('id').AsString;
   end;
 end;

Так что в моей базе всего одна строчка. Но программа добавляет много копий этой строки в StringGrid и вызывает ошибку (Индекс выходит за границы).


person Andrew Zitsew    schedule 12.04.2016    source источник
comment
Попробуйте SQLQUERY1.next внутри цикла и получите следующую запись. Если следующей записи нет, достигается EOF.   -  person moskito-x    schedule 12.04.2016
comment
Почему бы просто не использовать TDBGrid?   -  person Arioch 'The    schedule 12.04.2016


Ответы (1)


Опасно
Похоже, вы храните пароли в виде простого текста в базе данных.
Это крайне плохая идея.
Никогда не храните пароли в база данных.
Вместо этого используйте соленые хэши.
См. Как хешировать строку с помощью Delphi?

В вашем коде есть еще пара проблем:

  • Вы не гарантируете, что в строковой сетке достаточно строк для хранения ваших данных.
  • Вы не переходите к следующей строке запроса.
  • Вы открываете запрос до открытия соединения.
  • Вы используете FieldByName внутри цикла, это будет очень медленно.

Простое решение
Используйте DBGrid.

Если вы настаиваете на использовании StringGrid
, я предлагаю провести рефакторинг кода следующим образом:

 var 
   i,a:Integer;
   FUsername, FPasswordHash, Fid, FSalt: TField;
 begin
   if not(MySQl55Connection.Active) then MySql55Connection1.Open;
   SqlQuery1.SQL.Text:= 'SELECT * FROM users';  //only use backticks on reserved words.
   SqlQuery1.Open;
   FUsername:= SqlQuery1.FieldByName('Username');
   //do not use plain text passwords!!
   FPasswordHash:= SQLQuery1.FieldByName('SaltedPasswordHashUsingSHA256');
   FId:= SqlQuery1.FieldByName('id');
   FSalt:= SQLQuery1.FieldByName('SaltUsingCryptoRandomFunction');
   a:= StringGrid1.FixedRowCount;
   if SQLQuery1.RecordCount = -1 then StringGrid1.RowCount = 100 //set it to something reasonable.  
   else StringGrid1.RowCount:= a + SQLQuery1.RecordCount;
   //SQLQuery1.DisableControls 
   try
     i:= StringGrid1.FixedRowCount;
     while not(SQLQuery1.EOF) do begin
       if i >= StringGrid1.RowCount then StringGrid1.RowCount:= i;
       StringGrid1.Cells[0,i]:= FUserName.AsString;
       StringGrid1.Cells[1,i]:= FPasswordHash.AsString;
       StringGrid1,Cells[3,i]:= FSaltInHex.AsString;
       StringGrid1.Cells[2,i]:= FId.AsString;
       SQLQuery1.Next;  //get next row.
       Inc(i);
     end; {while}
   finally
     //just in case you want to do endupdate or close the SQLQuery or do SQLQuery1.EnableControls 
   end;
 end;

Пример базовой защиты
Вот как хешировать пароль:

Загрузите Lockbox3.
Поместите THash в свою форму и установите для свойства hash значение _5 _.
Используйте следующий код для получения результата хеширования.

function StringToHex(const input: string): AnsiString;
var
  NumBytes, i: Integer;
  B: Byte;
  W: word;
  Wa: array[0..1] of byte absolute W;
begin
  NumBytes := input.length * SizeOf(Char);
  SetLength(Result, NumBytes * 2);
  for i := 1 to NumBytes do begin
    if SizeOf(Char) = 1 then begin
      B:= Byte(input[i]);
      BinToHex(@B, @Result[(I*2)+1], 1);
    end else begin
      W:= Word(input[i]);
      BinToHex(@Wa[0], @Result[(i*4+0)],1);
      BinToHex(@Wa[1], @Result[(i*4+1)],1);
    end; {else}  
  end;
end;

function TForm1.HashPassword(var Password: string; const Salt: string): string;
var
  KillPassword: pbyte;
begin
  Hash1.HashString(StringToHex(Password)+StringToHex(Salt));
  KillPassword:= PByte(@Password[1]);
  FillChar(KillPassword^, Length(Password)*SizeOf(Char), #0); //remove password from memory.  
  Password:= ''; //Now free password.
end;

function GenerateSalt( ByteCount: integer = 32): string;
var
  Buffer: TMemoryStream;
begin
  Buffer := TMemoryStream.Create;
  try
    Buffer.Size := ByteCount;
    RandomFillStream( Buffer);
    result := Stream_to_Base64( Buffer);
  finally
    Buffer.Free
  end;
end;

Это минимальный объем работы, который вам может сойти с рук, пока все еще остается в безопасности.
Не думайте, что ваши пароли не важны, потому что у вас просто база данных игрушек, потому что люди повторно используют пароли и, таким образом, ваши пароли игрушек в конечном итоге становятся те же пароли, которые используются для онлайн-банкинга и тому подобное.
Люди ленивы ....

person Johan    schedule 12.04.2016
comment
ты был быстрее! Что ж, тогда нужно добавить две вещи: 1: RecordCount может просто возвращать -1 для запросов SQL. Тогда вам придется вычислить его с помощью дополнительного цикла. 2: вы должны уменьшить замедление мерцания экрана, вызвав StringGrid.Rows.BeginUpdate и freepascal.org/docs-html/fcl/db/tdataset.disablecontrols.html в блоке try-finally - person Arioch 'The; 12.04.2016
comment
@ Arioch'The, цикл for не будет выполняться, когда recordcount = -1. - person Johan; 12.04.2016
comment
@Johan, но он ДОЛЖЕН выполняться, потому что НОРМАЛЬНО возвращать -1 для SQL-запросов независимо от того, сколько строк там на самом деле. RecordCount требуется ТОЛЬКО при работе с локальными базами данных ISAM, такими как DBF и Paradox, никогда не требуется при работе с удаленным SQL. PS мы уже обсуждали это несколько месяцев назад? - person Arioch 'The; 12.04.2016
comment
вам следует использовать SELECT COUNT (*), потому что вы не можете доверять SQLQuery1.RecordCount - person moskito-x; 12.04.2016
comment
@ moskito-x нет, это тоже было бы неправильно. После того, как он вызовет COUNT и до того, как он заполнит сетку еще одним SELECT, кто-то еще может добавить в таблицу больше строк. Вы должны вызвать dataset.last, затем, БЕЗ ЗАКРЫТИЯ, вернуться к набору данных. БЕЗ подсчета строк, затем БЕЗ ЗАКРЫТИЯ установить счетчик строк сетки и заполнить его, выполняя итерацию вперед до EOF. Это все, что нужно сделать с подавлением мерцания экрана - person Arioch 'The; 12.04.2016
comment
@ Arioch'The: COUNT (*) должен быть частью первого и единственного SELECT - person moskito-x; 12.04.2016
comment
Я сомневаюсь, что это сработает, агрегирующие функции сворачивают множество строк в единичный результат. Если не используется GroupBy. Может быть, MYSQL позволит этот нестандартный трюк, не знаю - person Arioch 'The; 12.04.2016
comment
@Johan, я не помню, как в LCL, но, по крайней мере, в VCL TStringGrid не имеет методов BeginUpdate, но есть строковые списки Columns и Rows. Также, если SQLQuery - это TDataSet, тогда лучше вызывать DisableControls поверх него. - person Arioch 'The; 12.04.2016
comment
@ moskito-x если нет (MySQl55Connection.Active), то MySql55Connection1.Open; именно это. Он хочет открыть соединение, а не закрыть его. Хотя я бы удалил If-then и просто вызвал бы mysqlconnection.active: = true; пусть компонент сам проверит, был ли он уже открыт или еще нет - person Arioch 'The; 12.04.2016
comment
Также следует закрыть сам запрос после цикла - person Arioch 'The; 12.04.2016
comment
@Johan if not(MySQl55Connection.Active) then MySql55Connection1.Open; в порядке ОК. Я что-то напутал :-( извините за это. - person moskito-x; 12.04.2016
comment
Я отбросил свой наполовину ответ, чтобы не воссоздавать его из вашего. Сейчас я консультант, большие гонорары и никакой ответственности :-P - person Arioch 'The; 12.04.2016
comment
Кстати, with StringGrid1 do RowCount := RowCount +1; было бы плохо с точки зрения производительности. Если вы хотите избежать двойного цикла, то подход TList будет лучше: начните с разумного минимума, например, 16 строк, внутри цикла выполните if RowCount <=i then RowCount := RowCount * 2 и сократитесь до i+1 после цикла. Тем не менее, я думаю, что двойной цикл и одноразовый набор длины сетки были бы быстрее. Эмпирическое правило состоит в том, что несколько изменений в невизуальных компонентах обычно происходят быстрее, чем один обход визуальных слоев ОС. - person Arioch 'The; 12.04.2016
comment
@ Arioch'The: Как я могу это сделать ?? - person moskito-x; 12.04.2016
comment
@ moskito-x, обычно вы можете принять ответ с задержкой в ​​несколько часов. Это нужно для того, чтобы дать другим людям время придумать лучший ответ. Со временем появится галочка (серого цвета). Вы можете щелкнуть по нему и, таким образом, принять ответ как удовлетворительный ответ на ваш вопрос. - person Johan; 12.04.2016
comment
@Johan: извините, я не OP ;-) - person moskito-x; 12.04.2016
comment
@ moskito-x, черт возьми! Вы меня здесь! :-D и тогда я ввел Йохана в заблуждение :-( - person Arioch 'The; 12.04.2016
comment
@AndrewZitsew MEMO означает текстовый BLOB. Я предполагаю, что для ваших полей вам лучше использовать VARCHAR (n) (MySQL не хватает, поэтому вам придется использовать вместо этого CHAR) Тип SQL, чем BLOB ... Попробуйте изменить эти типы данных столбца с blob на строки или, по крайней мере, преобразовать их в строки в сам запрос - stackoverflow.com/questions/15368753 - person Arioch 'The; 13.04.2016
comment
Также, если ваша проблема представляет данные, возможно, правильным подходом было бы настроить сетку БД или использовать более продвинутую сетку БД? Например, JVCL был перенесен на Lazarus. CodeTyphon поставляется со многими типами дополнительных компонентов, например. Я думаю, ты ошибся в начале пути. Вам лучше продолжать использовать элементы управления с поддержкой db, потому что они не приведут к сбою вашего приложения из-за нехватки памяти при запросе 100 миллионов строк и не будут ждать несколько минут, прежде чем отобразить первые 10 строк 1M запроса, как при подходе ur StringGrid FetchAll. catb.org/esr/faqs/smart-questions.html#goal < / а> - person Arioch 'The; 13.04.2016