Recent

Author Topic: Access Violation - Database is Locked  (Read 2647 times)

egsuh

  • Hero Member
  • *****
  • Posts: 1710
Re: Access Violation
« Reply #15 on: November 14, 2025, 02:45:05 am »
How is FrmClientsMgt created? It is not created automatically.

Thausand

  • Sr. Member
  • ****
  • Posts: 445
Re: Access Violation
« Reply #16 on: November 14, 2025, 03:22:23 am »
Why a DataModule???
Manual https://www.freepascal.org/docs-html/rtl/classes/tdatamodule.html

Quote
TDataModule is a container for non-visual objects which can be used in an IDE to group non-visual objects which can be used by various other containers (forms) in a project. Notably, data access components are typically stored on a datamodule. Web components and services can also be implemented as descendants of datamodules.

TDataModule introduces some events which make it easier to program, and provides the needed streaming capabilities for persistent storage.

An IDE will typically allow to create a descendant of TDataModule which contains non-visual components in it's published property list.
That is short why  :)

Then have think. Why have form make clutter with non-visual component ? When use datamodule then have group database function for one location. That important for more easy for maintain. When have database components for form then have multiple form then when want change for database then have visit and change all form (unit code/component property).

Use datamodule not have mandator but have convenient. Can make convenient when use normal unit. When have database then is no require visual component. Visual database component is same when datamodule and is convenient.

Thausand

  • Sr. Member
  • ****
  • Posts: 445
Re: Access Violation
« Reply #17 on: November 14, 2025, 03:33:07 am »
Hi, I see i one thing that can produce issue: the data module is created BEFORE the main form.
Someone more experimented than me can say if it is ?
I no expert. but have sense logic :)

Answer is depend. When want database setup before form show and form have property connect then form most have depend database then datamodule first, form after. When form is make open database and connect runtime then is form first then have datamodule.

It can have complicate many things when user make use many event connection when develop. Example: when have event formshow and then have code for query database then database is first setup, after then form. There many event both form and database. So when event depend when database or form available then have impact order creation.

So I have personal think that create database visual and have set event properties IDE is no good for understand how is impact form and database. Is happen many time that user have violation access (and why have no idea because have not learn how is work this impact)

Handoko

  • Hero Member
  • *****
  • Posts: 5506
  • My goal: build my own game engine using Lazarus
Re: Access Violation
« Reply #18 on: November 14, 2025, 04:31:59 am »
So I have personal think that create database visual and have set event properties IDE is no good for understand how is impact form and database.
Me too avoid setting events for database visual components. In the past I like the idea of database visual components, setting the AfterInsert, AfterDelete events, etc. At the beginning that seemed good, but it became harder to understand the flow. Now, I put all the database related code into a single unit. Functions and procedures are all called by code not events. I separate properly the database and UI parts. The code now becomes easier to follow, extend and debug.

Thaddy

  • Hero Member
  • *****
  • Posts: 18521
  • Here stood a man who saw the Elbe and jumped it.
Re: Access Violation
« Reply #19 on: November 14, 2025, 06:23:59 am »
I totally agree with that. This week, I happened to stumble into some code I wrote in the 1996/7/8 era with Delphi 1 and 2:
OMG what a mess. Can't believe I got any money for that! I must have been so into that code that nobody else could make any sense of it and that includes me after 28 odd years. It was around then that we decided all the database stuff should be done in code, not through the screendrawing properties. Things got better when the actionlist and actions were introduced and we introduced the rule that we only write IDE's based on actions, never direct. That code, from the early 2000's looks a lot better!
But as with any programming it takes some discipline, but makes a team and yourself a lot more productive.
The 1996/7 code shows its age: hey Delphi is great, we need to do everything visually. NOT. It happened to be a false promise in many ways.
Things like Datamodules and Action lists were the onset of better programming when combined.
Writing more in code helps a lot regarding quality and maintenance.
After all, a good program is usually not the userinterface, although it is part of its usefulness.
Many should un-learn user interface centric programming, a.k.a. screen drawing, because it is plain wrong.
Wrong in the sense that perfectly good tools are used in the wrong way.

All this because I found an USB floppy drive in the attick along with some floppies that were not deceased yet..
Now all I need is an USB ide interface for my BigFoots to be able to see what other damage I have caused in the past... :P
<shiver>

(D2 was distributed on around 20! floppies)
« Last Edit: November 14, 2025, 06:41:33 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

1HuntnMan

  • Sr. Member
  • ****
  • Posts: 408
  • From Delphi 7 to Lazarus
    • NewFound Photo Art
Re: Access Violation
« Reply #20 on: November 16, 2025, 04:48:40 pm »
Yup, I finally figured out most of all my issues.  When I first started on this project, I had all the Queries defined on the DataModule and then changed my designed and just had the Queries for just the lookup tables, i.e. States/Provinces, Countries, etc. on the DM and moved my Queries for the main units/forms to their forms/units, i.e. Appointments, Clients, ContactsMgt., etc.  Just discovered that all the Query-Field declarations were left in the DM-interface section, i.e. QryAppmts: TSQLQuery, QryAppmtsACTIVE:TBooleanField;, etc.  So, those queries were deleted and moved to their own units and all this was left behind but when I compiled, it didn't catch these left behind.  So, decided to move everything back like before and the Clients. Appointments, ContactsMgt forms just have DataSouces components on their forms pointing back to their Queries. But just working with just the Appointments form which now just has DataSource components on the form for all the DBEdits, DBNav, DBGrid, etc.  I can code for example:
Code: Pascal  [Select][+][-]
  1. DSrcAppmts.DataSet:= PMSDataModule.QryAppmts;
  2. DSrcCntks.DataSet:= PMSDataModule.QryCntks;
  3. etc.
  4.  

But, the actual Appointment form DataSource component can't see the DataModule.  I can assign in code but if I click on the DataSource on the form and attempt to assign the DataSet property, the dropdown doesn't reveal the DataModule Queries.  I have the datamodule in the USES in the InterFace so tried defining a USES DM in the implementation but Lazarus doesn't see the DM???????

1HuntnMan

  • Sr. Member
  • ****
  • Posts: 408
  • From Delphi 7 to Lazarus
    • NewFound Photo Art
Re: Access Violation
« Reply #21 on: November 17, 2025, 11:09:54 pm »
Figured this out, had to learn a bit about inline I guess, use of procedures since all database everything is now in the DataModule.  So, an AfterInsert or AfterDelete in a unit that's using the DataModule learned this and works:

Code: Pascal  [Select][+][-]
  1. QryPropsls.AfterInsert:= @PMSDataModule.QryPropslsAfterInsert;
  2. QryPropsls.AfterDelete:= @PMSDataModule.QryPropslsAfterDelete;
  3.  

That's all folks, thanks for all the education!

Thausand

  • Sr. Member
  • ****
  • Posts: 445
Re: [SOLVED] Access Violation
« Reply #22 on: November 17, 2025, 11:29:25 pm »
What write Thaddy is good example why I think is better for make setup and have use write code.

When have old project and use visual property setting then I no know or have remember what property is set. This special true for database.

When make setup when use code then can
- see what is code do
- have code line comment for explain what code is do

I have make many waste time when work for project that have many coder because I have make watch all property and see what is set and connect.

All person that work on project make change personal and have make time for detection what is change to make fix problem I have work for.

Visual develop is concept but when have example many database and have team work then is no practible.

This personal think. It no wrong when have different practice. For me is simple no practible. When example have problem and post problem on forum and not show all property (or have database same) then how can see or tell what wrong ? I have see this write happen many time on forum and I think  is waste many time.

Thank Thaddy for example and good explain.

1HuntnMan

  • Sr. Member
  • ****
  • Posts: 408
  • From Delphi 7 to Lazarus
    • NewFound Photo Art
Re: Access Violation - Database is Locked
« Reply #23 on: December 05, 2025, 03:23:43 pm »
Okay, put this back from SOLVED to UNSOLVED.  Discovered something by accident. This morning, I didn't launch Lazarus.  Testing by running from within Lazarus I kept getting "Database is Locked" everytime I run debugging and getting this error whenever I saved an edit to a record see the code below. But, ran the app from the executable [PMS.exe] and no errors?????? WTF!

On Save event code:
Code: Pascal  [Select][+][-]
  1. procedure TFrmAppmnts.BitBtnSaveClick(Sender: TObject);
  2. begin
  3.   if QryAppmts.State in [dsEdit, dsInsert] then
  4.     begin
  5.       QryAppmts.Post;
  6.       QryAppmts.ApplyUpdates;
  7.       {SaveQueryChanges(QryAppmts,PMSDataModule.TransPMSDB);
  8.       EnsureQueryReady(QryAppmts, True);}
  9.     end;
  10. end;
  11.  

The SaveQueryChanges and EnsureQueryReady is just a procedure(s) I put in the DataModule but above I Commit the Transaction in the Query property:

Code: Pascal  [Select][+][-]
  1. procedure SaveQueryChanges(Q: TSQLQuery; Trans: TSQLTransaction);
  2. begin
  3.   if Q = nil then Exit;
  4.  
  5.   // 1) Finalize the record (if in edit/insert)
  6.   if Q.State in [dsEdit, dsInsert] then
  7.     Q.Post;
  8.  
  9.   // 2) Put all changes to the DB (harmless if sqoAutoApplyUpdates already ran)
  10.   Q.ApplyUpdates;
  11.  
  12.   // 3) commit (use Commit; CommitRetaining is not present on all builds)
  13.   if Assigned(Trans) and Trans.Active then
  14.   begin
  15.     Trans.CommitRetaining;  //=> Durable Write
  16.     { If you want “retaining-like” behavior during normal use,
  17.       immediately restart: Trans.StartTransaction;
  18.       CommitRetaining keeps the transaction open/active →
  19.       calling StartTransaction again throws “already active”.
  20.       Commit ends the transaction → you may want to immediately
  21.       StartTransaction again for long-lived UIs. }
  22.     Trans.StartTransaction;
  23.   end;
  24. end;
  25.  

I'm happy but my last question, most of the advice was to hard code the properties for the Query???? Are you saying to not put a Query component on the DataModule or app unit form or the Transaction component on the form, hard code that plus all the properties of the Connection/Transaction and the query and datasource????

Handoko

  • Hero Member
  • *****
  • Posts: 5506
  • My goal: build my own game engine using Lazarus
Re: Access Violation - Database is Locked
« Reply #24 on: December 05, 2025, 06:24:47 pm »
By no means am I an expert in database programming. But I want to share some tips in coding. I don't mean to criticize, oppositely I really appreciate 1HuntnMan's persistent effort to make the code work. I hope my explanation can be useful.


On Save event code:
Code: Pascal  [Select][+][-]
  1. procedure TFrmAppmnts.BitBtnSaveClick(Sender: TObject);
  2. begin
  3.   if QryAppmts.State in [dsEdit, dsInsert] then
  4.     begin
  5.       QryAppmts.Post;
  6.       QryAppmts.ApplyUpdates;
  7.       {SaveQueryChanges(QryAppmts,PMSDataModule.TransPMSDB);
  8.       EnsureQueryReady(QryAppmts, True);}
  9.     end;
  10. end;
  11.  

The code above is a big no. All experienced programmers should tell you "separate the UI part and the logic part", the logic part here means the database related commands. The BitBtnSaveClick should call the related procedure (or a function) in your database unit, not accessing the database commands directly. All the database related commands or procedures or functions should be put in a single unit file, or module, or library or package.

You may ask why. If the project contains just 300 lines of code or less, your approach should work fine. But if it needs to access multiple tables, master-detail relationship, and many technical rules, then the code will be very hard to extend and debug. Many 'weird' things may happen just as what you experiencing now. Programming doesn't just mean to make it work, you have to put effort to make your life easier in the future by properly separate and group them into several easy to maintain parts.

So, how to make the code above better? Something like this:

Code: Pascal  [Select][+][-]
  1. procedure TFrmAppmnts.BitBtnSaveClick(Sender: TObject);
  2. begin
  3.   if not(DataUnit.SaveAppmnts(param1, param2, param3, ...)) then
  4.     ShowMessage('Failed to save appointment data');
  5. end;
  6.  

In the code above, the form does only what it should do: collecting user inputs and actions, and showing message back to the user. If it needs to access the database, it simply call a related procedure/function and pass all the needed information via its parameters. The BitBtnSave should not call any database commands directly.

It may seem trivial, but if you abandon your old code but rewrite them as what I said, you will thank yourself doing so. I dare to say so because when I started learning database programming, I wrote the code just as the way you're doing now. Visual programming sounded good, I dropped some components onto the form, setting some properties and put some database related commands in the form. What a pain maintaining and debugging the code. My code worked but it's really messy.

Below is the upper part of uniData.pas - my recent project, for you to analyze. I can't post the whole code here because it has more than 1000 lines. It is in Indonesian.

Code: Pascal  [Select][+][-]
  1. unit uniData;
  2.  
  3. // SQLite client version: 3.42.0 on Linux, 3.45.1
  4.  
  5. {$mode ObjFPC}{$H+}
  6.  
  7. interface
  8.  
  9. uses
  10.   Classes, SysUtils, Controls, Dialogs, StdCtrls, Grids, ExtCtrls, Math,
  11.   uniStorage;
  12.  
  13. type
  14.   TSetting = record
  15.     DataFileName:       string[30];
  16.     ThresholdTransaksi: (ttJual, ttBeli);
  17.     NamaMataUangUtama:  string[10];
  18.     DeskMataUangUtama:  string[20];
  19.     NamaMataUangThres:  string[10];
  20.     DeskMataUangThres:  string[20];
  21.     NamaMataUangLen:    Byte;
  22.     DeskMataUangLen:    Byte;
  23.     NilaiMaksimum:      integer;
  24.     NilaiDesimal:       Byte;
  25.   end;
  26.  
  27. var
  28.   Setting: TSetting;
  29.  
  30. Type
  31.   TTableName = (tblMataUang,tblRate, tblNasabah, tblNasabahJenis,
  32.     tblWargaNegara, tblPekerjaan, tblTransaksi);
  33.  
  34. function  isStringTooLong(var S: string; Len: Integer): Boolean;
  35. function  isEditTooLong(Edit: TEdit; Len: Integer): Boolean;
  36. function  isEditTooLong(lbeEdit: TLabeledEdit; Len: Integer): Boolean;
  37. function  isValueBad(Value: string): Boolean;
  38. procedure SanitizeValue(var Value: Double);
  39. procedure SanitizeValue(var Value: string);
  40. procedure SanitizeValue(lbeEdit: TLabeledEdit);
  41. function  ErrorMessage: string;
  42. procedure CreateDatabase;
  43. function  ConfirmDelete(const Info: string): Boolean;
  44. procedure TableBrowse(tbl: TTableName; Grid: TStringGrid; Page: Integer);
  45. procedure TableDelete(tbl: TTableName; const ID: string);
  46. function  TableRecordCount(tbl: TTableName) : Integer;
  47.  
  48. // Transaksi
  49. procedure AddTransaksi(const Nasabah, MataUang, Rate, Waktu, Beli, Jual,
  50.   Total, Threshold: string);
  51. procedure EditTransaksi(const ID, Nasabah, MataUang, Rate, Beli, Jual,
  52.   Total, Threshold: string);
  53. function  GetTransaksiNasabah(const ID: string): string;
  54. function  GetTransaksiMataUang(const ID: string): string;
  55. function  GetTransaksiWaktu(const ID: string; Format: Boolean): string;
  56. function  isTransaksiBad(const Nasabah, MataUang, Rate, Waktu, Beli, Jual:
  57.   string): Boolean;
  58. // Mata Uang
  59. procedure AddMataUang(const Nama, Deskripsi: string);
  60. procedure EditMataUang(const ID, Nama, Deskripsi: string);
  61. function  GetMataUangNama(const ID: string): string;
  62. function  isMataUangBad(const ID, Nama, Deskripsi: string): Boolean;
  63. // Rate
  64. procedure AddRate(const Waktu, MataUang, Jual, Beli: string);
  65. procedure EditRate(const ID, MataUang, Jual, Beli: string);
  66. function  GetRateMataUang(const ID: string): string;
  67. function  GetRateWaktu(Const ID: string; Format: Boolean): string;
  68. function  isRateBad(const ID, Waktu, MataUang, Jual, Beli: string): Boolean;
  69. function  GetRate(const MataUang, Waktu: string; strResult: TStringList):
  70.           Boolean;
  71. function  GetRateBeli(const MataUang, Waktu: string): string;
  72. function  GetRateJual(const MataUang, Waktu: string): string;
  73. function  GetRateMiddle(const Waktu: string): string;
  74. // Nasabah
  75. procedure AddNasabah(const Nama, Jenis, Identitas, WargaNegara, Pekerjaan:
  76.   string);
  77. procedure EditNasabah(const ID, Nama, Jenis, Identitas, WargaNegara, Pekerjaan:
  78.   string);
  79. function  GetNasabahNama(const ID: string): string;
  80. function  GetNasabahJenis(const ID: string): string;
  81. function  GetNasabahWargaNegara(Const ID: string): string;
  82. function  GetNasabahPekerjaan(Const ID: string): string;
  83. function  isNasabahBad(const ID, Nama, Jenis, Identitas, WargaNegara, Pekerjaan:
  84.           string): Boolean;
  85. // Nasabah Jenis
  86. procedure AddNasabahJenis(const Deskripsi: string);
  87. procedure EditNasabahJenis(const ID, Deskripsi: string);
  88. function  GetJenisDeskripsi(const ID: string): string;
  89. function  isNasabahJenisBad(const ID, Deskripsi: string): Boolean;
  90. // Warga Negara
  91. procedure AddWargaNegara(const Deskripsi: string);
  92. procedure EditWargaNegara(const ID, Deskripsi: string);
  93. function  GetWargaNegaraDeskripsi(const ID: string): string;
  94. function  isWargaNegaraBad(const ID, Deskripsi: string): Boolean;
  95. // Pekerjaan
  96. procedure AddPekerjaan(const Deskripsi: string);
  97. procedure EditPekerjaan(const ID, Deskripsi: string);
  98. function  GetPekerjaanDeskripsi(const ID: string): string;
  99. function  isPekerjaanBad(const ID, Deskripsi: string): Boolean;
  100. // Setting
  101. procedure AddSetting(const Threshold, Maksimum, Desimal: string);
  102. procedure ReadSettings;
  103.  
  104. const
  105.   DataFileName                = 'vnsData.sqlite';
  106.   DateTimeFormat              = 'dd-mm-yy hh:nn:ss';
  107.   MataUangNamaLength          = 10;
  108.   MataUangDeskripsiLength     = 20;
  109.   NasabahNamaLength           = 30;
  110.   NasabahIdentitasLength      = 20;
  111.   NasabahJenisDeskripsiLength = 20;
  112.   WargaNegaraDeskripsiLength  = 35;
  113.   PekerjaanDeskripsiLength    = 30;
  114.  
  115. implementation
  116.  
  117. uses
  118.   fruWaktu;
  119.  
  120. function  TableName(tbl: TTableName): string; forward;
  121. procedure PrepareData; forward;
  122. procedure DoQuery(const S: string); forward;
  123. function  isTableHasID(tbl: TTableName; const ID: string): Boolean; forward;
  124. procedure RateCallBack(Sender: TObject; AData: Pointer); forward;
  125.  
  126. const
  127.   // Transaksi
  128.   sqlCreateTransaksi =
  129.     'CREATE TABLE tblTransaksi ( '                    +
  130.     'ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' +
  131.     'Nasabah INTEGER NOT NULL, '                      + // NasabahID
  132.     'MataUang INTEGER NOT NULL, '                     + // MataUangID
  133.     'Rate INTEGER NOT NULL, '                         + // RateID
  134.     'Waktu REAL NOT NULL, '                           +
  135.     'Beli REAL NOT NULL, '                            +
  136.     'Jual REAL NOT NULL, '                            +
  137.     'Total REAL NOT NULL, '                           +
  138.     'Threshold REAL NOT NULL ) STRICT;';
  139.   sqlAddTransaksi =
  140.     'INSERT INTO tblTransaksi (Nasabah, MataUang, Rate, Waktu, Beli, Jual, ' +
  141.     'Total, Threshold) VALUES (''';
  142.   sqlDeleteTransaksi =
  143.     'DELETE FROM tblTransaksi WHERE ID=''';
  144.   sqlEditTransaksi =
  145.     'UPDATE tblTransaksi SET ';
  146.   sqlBrowseTransaksi =
  147.     'Transaksi.ID, Waktu, Nsb.Nama, Uang.Nama, Jual, Beli, Total, Threshold ' +
  148.     'FROM tblTransaksi AS Transaksi '                                +
  149.     'LEFT JOIN tblMataUang AS Uang ON Transaksi.MataUang=Uang.ID '        +
  150.     'LEFT JOIN tblNasabah AS Nsb ON Nasabah=Nsb.ID';
  151.   // Mata Uang
  152.   sqlCreateMataUang =
  153.     'CREATE TABLE tblMataUang ( '                     +
  154.     'ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' +
  155.     'Nama TEXT NOT NULL UNIQUE, '                     +
  156.     'Deskripsi TEXT NOT NULL UNIQUE, '                +
  157.     'CHECK (length(Nama) <= 10 AND length(Deskripsi) <= 20) ) STRICT;';
  158.   sqlAddMataUang =
  159.     'INSERT INTO tblMataUang (Nama, Deskripsi) VALUES (''';
  160.   sqlDeleteMataUang =
  161.     'DELETE FROM tblMataUang WHERE ID=''';
  162.   sqlEditMataUang =
  163.     'UPDATE tblMataUang SET ';
  164.   sqlTestMataUangNama =
  165.     'SELECT * FROM tblMataUang WHERE Nama=''';
  166.   sqlTestMataUangDeskripsi =
  167.     'SELECT * FROM tblMataUang WHERE Deskripsi=''';
  168.   // Rate
  169.   sqlCreateRate =
  170.     'CREATE TABLE tblRate ( '                         +
  171.     'ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' +
  172.     'Waktu REAL NOT NULL, '                           +
  173.     'MataUang INTEGER NOT NULL, '                     +
  174.     'Beli REAL NOT NULL, '                         +
  175.     'Jual REAL NOT NULL, '                 +
  176.     'Tengah REAL NOT NULL ) STRICT;';
  177.   sqlAddRate =
  178.     'INSERT INTO tblRate (Waktu, MataUang, Beli, Jual, Tengah) VALUES (''';
  179.   sqlDeleteRate =
  180.     'DELETE FROM tblRate WHERE ID=''';
  181.   sqlEditRate =
  182.     'UPDATE tblRate SET ';
  183.   sqlBrowseRate =
  184.     'Rate.ID, Waktu, Uang.Nama, Jual, Beli, Tengah FROM tblRate ' +
  185.     'AS Rate LEFT JOIN tblMataUang AS Uang ON Rate.MataUang=Uang.ID';
  186.   // Nasabah
  187.   sqlCreateNasabah =
  188.     'CREATE TABLE tblNasabah ( '                      +
  189.     'ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' +
  190.     'Nama TEXT NOT NULL, '                            +
  191.     'Jenis INTEGER NOT NULL, '                        +
  192.     'Identitas TEXT NOT NULL UNIQUE, '                +
  193.     'WargaNegara INTEGER NOT NULL, '                  +
  194.     'Pekerjaan INTEGER NOT NULL, '                    +
  195.     'CHECK (length(Nama) <= 30 AND length(Identitas) <= 20) ) STRICT;';
  196.   sqlAddNasabah =
  197.     'INSERT INTO tblNasabah ' +
  198.     '(Nama, Jenis, Identitas, WargaNegara, Pekerjaan) VALUES (''';
  199.   sqlDeleteNasabah =
  200.     'DELETE FROM tblNasabah WHERE ID=''';
  201.   sqlEditNasabah =
  202.     'UPDATE tblNasabah SET ';
  203.   sqlTestNasabahIdentitas =
  204.     'SELECT * FROM tblNasabah WHERE Identitas=''';
  205.   sqlBrowseNasabah =
  206.     'Nsb.ID, Nama, Jns.Deskripsi, Identitas, Wga.Deskripsi, '    +
  207.     'Pkj.Deskripsi FROM tblNasabah AS Nsb '                      +
  208.     'LEFT JOIN tblNasabahJenis AS Jns ON Nsb.Jenis=Jns.ID '      +
  209.     'LEFT JOIN tblWargaNegara AS Wga ON Nsb.WargaNegara=Wga.ID ' +
  210.     'LEFT JOIN tblPekerjaan As Pkj On Nsb.Pekerjaan=Pkj.ID';
  211.   // NasabahJenis
  212.   sqlCreateNasabahJenis =
  213.     'CREATE TABLE tblNasabahJenis ( '                 +
  214.     'ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' +
  215.     'Deskripsi TEXT NOT NULL UNIQUE, '                +
  216.     'CHECK(length(Deskripsi) <= 20) ) STRICT;';
  217.   sqlAddNasabahJenis =
  218.     'INSERT INTO tblNasabahJenis (Deskripsi) VALUES (''';
  219.   sqlDeleteNasabahJenis =
  220.     'DELETE FROM tblNasabahJenis WHERE ID=''';
  221.   sqlEditNasabahJenis =
  222.     'UPDATE tblNasabahJenis SET Deskripsi=''';
  223.   sqlTestNasabahJenisDeskripsi =
  224.     'SELECT * FROM tblNasabahJenis WHERE Deskripsi=''';
  225.   // WargaNegara
  226.   sqlCreateWargaNegara =
  227.     'CREATE TABLE tblWargaNegara ( '                  +
  228.     'ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' +
  229.     'Deskripsi TEXT NOT NULL UNIQUE, '                +
  230.     'CHECK(length(Deskripsi) <= 35) ) STRICT;';
  231.   sqlAddWargaNegara =
  232.     'INSERT INTO tblWargaNegara (Deskripsi) VALUES (''';
  233.   sqlDeleteWargaNegara =
  234.     'DELETE FROM tblWargaNegara WHERE ID=''';
  235.   sqlEditWargaNegara =
  236.     'UPDATE tblWargaNegara SET Deskripsi=''';
  237.   sqlTestWargaNegaraDeskripsi =
  238.     'SELECT * FROM tblWargaNegara WHERE Deskripsi=''';
  239.   // Pekerjaan
  240.   sqlCreatePekerjaan =
  241.     'CREATE TABLE tblPekerjaan ( '                    +
  242.     'ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ' +
  243.     'Deskripsi TEXT NOT NULL UNIQUE, '                +
  244.     'CHECK(length(Deskripsi) <= 30) ) STRICT;';
  245.   sqlAddPekerjaan =
  246.     'INSERT INTO tblPekerjaan (Deskripsi) VALUES (''';
  247.   sqlDeletePekerjaan =
  248.     'DELETE FROM tblPekerjaan WHERE ID=''';
  249.   sqlEditPekerjaan =
  250.     'UPDATE tblPekerjaan SET Deskripsi=''';
  251.   sqlTestPekerjaanDeskripsi =
  252.     'SELECT * FROM tblPekerjaan WHERE Deskripsi=''';
  253.   // General
  254.   sqlWhereID =
  255.     ' WHERE ID=''';
  256.   // Setting
  257.   sqlCreateSetting =
  258.     'CREATE TABLE tblSetting ( '                                      +
  259.     'ThresholdTransaksi INTEGER NOT NULL, '                           +
  260.     'NilaiMaksimum INTEGER NOT NULL, '                                +
  261.     'NilaiDesimal INTEGER NOT NULL, '                                 +
  262.     'CHECK (ThresholdTransaksi >= 0 AND ThresholdTransaksi <= 1 AND ' +
  263.     'NilaiMaksimum >= 1000000 AND NilaiMaksimum <= 1000000000 AND '   +
  264.     'NilaiDesimal >= 0 AND NilaiDesimal <= 3) ) STRICT;';
  265.   sqlAddSetting =
  266.     'INSERT INTO tblSetting ' +
  267.     '(ThresholdTransaksi, NilaiMaksimum, NilaiDesimal) VALUES (''';
  268.  
  269. const
  270.   // Transaksi
  271.   ErrTransaksiRateNotFound =
  272.     'Belum ada rate untuk mata uang tersebut,' + LineEnding +
  273.     'silahkan diisikan ratenya terlebih dahulu.';
  274.   ErrTransaksiEmpty =
  275.     'Semua item tidak boleh kosong.';
  276.   ErrTransaksiNilai =
  277.     'Nilai harus di atas 0 dan maksimum ';
  278.   // Mata Uang
  279.   ErrMataUangIDNotFound =
  280.     'Internal error, MataUangID.';
  281.   ErrMataUangEmpty =
  282.     'Nama dan Keterangan tidak boleh kosong.';
  283.   ErrMataUangDuplicate =
  284.     'Mata uang tersebut sudah ada,' + LineEnding +
  285.     'silahkan isikan yang lain.';
  286.   ErrMataUangLengthNama =
  287.     'Maksimum 10 character untuk nama mata uang.';
  288.   ErrMataUangLengthDesc =
  289.     'Maksimum 20 character untuk keterangan mata uang.';
  290.   // Rate
  291.   ErrRateEmpty =
  292.     'Semua item tidak boleh kosong.';
  293.   ErrRateBeliValue =
  294.     'Rate beli harus di atas 0 dan maksimum ';
  295.   ErrRateJualValue =
  296.     'Rate jual harus di atas 0 dan maksimum ';
  297.   // Nasabah
  298.   ErrNasabahIDNotFound =
  299.     'Internal error, NasabahID.';
  300.   ErrNasabahEmpty =
  301.     'Semua item tidak boleh kosong.';
  302.   ErrNasabahDuplicateDesc =
  303.     'Nasabah dengan identitas tersebut sudah ada.';
  304.   ErrNasabahLengthNama =
  305.     'Maksimum 30 character untuk nama nasabah.';
  306.   ErrNasabahLengthIdentitas =
  307.     'Maksimum 20 character untuk identitas nasabah.';
  308.   // NasabahJenis
  309.   ErrNasabahJenisIDNotFound =
  310.     'Internal error, NasabahJenisID.';
  311.   ErrNasabahJenisEmpty =
  312.     'Jenis nasabah tidak boleh kosong.';
  313.   ErrNasabahJenisDuplicateDesc =
  314.     'Jenis nasabah tersebut sudah ada,' + LineEnding +
  315.     'silahkan isikan jenis nasabah yang lain.';
  316.   ErrNasabahJenisLengthDesc =
  317.     'Maksimum 20 character untuk jenis nasabah.';
  318.   // WargaNegara
  319.   ErrWargaNegaraIDNotFound =
  320.     'Internal error, WargaNegaraID.';
  321.   ErrWargaNegaraEmpty =
  322.     'Warga negara tidak boleh kosong.';
  323.   ErrWargaNegaraDuplicateDesc =
  324.     'Warga negara tersebut sudah ada,' + LineEnding +
  325.     'silahkan isikan nama warga negara yang lain.';
  326.   ErrWargaNegaraLengthDesc =
  327.     'Maksimum 35 character untuk nama warga negara.';
  328.   // Pekerjaan
  329.   ErrPekerjaanIDNotFound =
  330.     'Internal error, PekerjaanID.';
  331.   ErrPekerjaanEmpty =
  332.     'Pekerjaan tidak boleh kosong.';
  333.   ErrPekerjaanDuplicateDesc =
  334.     'Pekerjaan tersebut sudah ada,' + LineEnding +
  335.     'silahkan isikan nama pekerjaan yang lain.';
  336.   ErrPekerjaanLengthDesc =
  337.     'Maksimum 30 character untuk nama pekerjaan.';
  338.  
  339. var
  340.   Data:     TSQLite = nil;
  341.   ErrorMsg: string  = '';
  342. ...
  343.  

1. Line #14..#28
I used TSetting because the code was intended to allow users to open different data files each with its own settings.

2. Line #30..#32
The program has 7 tables as defined in the TTableName. I needed to do so because I want to centralize similar code into same location. See no. 5 below.

3. Line #34..#40
These are for user input validation and sanitizing bad input. I passed TEdit and TLabelEdit directly because I don't want to convert them to string when passing them as parameters.

4. Line #41
The last database error, user incorrect input message and validation error is stored and can be showed by calling ErrorMessage function.

5. Line #42..#46
These are the basic database access. Notice the use of TTableName, I can use same code but for different tables.

6. Line #48..#103
These are for the form to connect to the database, like loading data, clicking save and edit buttons.

7. Line #108..#114
These are the allowed lengths for the user inputs, used for validating user inputs. See no. 8 below.

8. Line #56, #62, #68, #83, #89, #94, #99
These are for validating user inputs before saving them to its related tables.  If the value provided by user is unacceptable, it will store the error message (see no. 10) and the value can be accessed by the user input form by calling ErrorMessage function (see no. 4).

9. Line #127..#268
I put all the sql query code here. So I can easily read, compare, and extend. If I need a new table, I just copy-paste here and do some edit. All of them are centralized in the same location. For easy maintain and debugging.

10. Line #270..#338
Putting all the error messages in the same location.


As you can see, my code is well organized. If I want to add new feature for the data, I know where to add. If I found something weird, I can easily test each part.
« Last Edit: December 05, 2025, 06:41:48 pm by Handoko »

cdbc

  • Hero Member
  • *****
  • Posts: 2522
    • http://www.cdbc.dk
Re: Access Violation - Database is Locked
« Reply #25 on: December 05, 2025, 07:19:09 pm »
Hi
Well done Handoko =^
Regards Benny
If it ain't broke, don't fix it ;)
PCLinuxOS(rolling release) 64bit -> KDE6/QT6 -> FPC Release -> Lazarus Release &  FPC Main -> Lazarus Main

Thausand

  • Sr. Member
  • ****
  • Posts: 445
Re: Access Violation - Database is Locked
« Reply #26 on: December 05, 2025, 08:25:02 pm »
Well done Handoko =^
That +1  :)

When do what do Handoko many time then have come experience that many things are same. Then can have create separate class(es) that have this functionality. Then can make separation gui en database logic more easy. That is when is use very big database and have many many tables. It have logic progress.

When design database and use IDE design this nice for quick small database and have learn how is work database basic very quick (RAD). Then want extend and have problem and error as write  Handoko. Then is better have use code and no use IDE design and have mixing GUI and database code.

Many time problem that make newbie is use formcreate and have all connection auto and give many problem and not find database, not find library, database is lock, directory no exist, server no connect etc. When use separate routine as write Handoko then error is not GUI/property but have code error. That more easy for debug and more easy for maintain (can have better customize).

This experience have come when make database program many time. Every person have favorite but when have need for maintain then better for maintain is have use code and no IDE design for database. Have note that is no need for manual create all database component and when make approach Handoko. When start then can have have use manual code create for connection (database, transaction, query). Is no judge but when is child then have little step then grow and make step more big. Many small step make travel for have reach same goal  :)

 

TinyPortal © 2005-2018