Stroika Library 3.0d18
 
Loading...
Searching...
No Matches
ORMEmployeesDB.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Frameworks/StroikaPreComp.h"
5
6#include <iostream>
7#include <random>
8
10#include "Stroika/Foundation/Containers/Set.h"
18
19#include "ORMEmployeesDB.h"
20
21using namespace std;
22
23using namespace Stroika::Foundation;
25using namespace Stroika::Foundation::Common;
27using namespace Stroika::Foundation::Database;
28using namespace Stroika::Foundation::Debug;
29using namespace Stroika::Foundation::Execution;
30
31using namespace Database::SQL;
32using namespace SQL::ORM;
33
34namespace {
35
36 struct Employee {
37 optional<int> ID{};
38 String fName;
39 int fAge{};
40 String fAddress;
41 double fSalary{};
42 bool fStillEmployed{};
43
44 static const ObjectVariantMapper kMapper;
45 };
46 /*
47 * Define mapping to VariantValues (think JSON)
48 */
49 const ObjectVariantMapper Employee::kMapper{[] () {
51 mapper.AddCommonType<optional<int>> ();
52 mapper.AddClass<Employee> (
53 {
54 {"id"sv, &Employee::ID},
55 {"Name"sv, &Employee::fName},
56 {"Age"sv, &Employee::fAge},
57 {"Address"sv, &Employee::fAddress},
58 {"Salary"sv, &Employee::fSalary},
59 {"Still-Employed"sv, &Employee::fStillEmployed},
60 },
61 {.fOmitNullEntriesInFromObject = false});
62 return mapper;
63 }()};
64
65 struct Paycheck {
66 optional<int> ID{};
67 int fEmployeeRef;
68 double fAmount{};
69 Time::Date fDate;
70
71 static const ObjectVariantMapper kMapper;
72 };
73 /*
74 * Define mapping to VariantValues (think JSON)
75 */
76 const ObjectVariantMapper Paycheck::kMapper{[] () {
78 mapper.AddCommonType<optional<int>> ();
79 mapper.AddClass<Paycheck> (
80 {
81 {"id"sv, &Paycheck::ID},
82 {"Employee-Ref"sv, &Paycheck::fEmployeeRef},
83 {"Amount"sv, &Paycheck::fAmount},
84 {"Date"sv, &Paycheck::fDate},
85 },
86 {.fOmitNullEntriesInFromObject = false});
87 return mapper;
88 }()};
89
90 /**
91 * Combine all the ObjectVariantMappers for the objects we use in this database into one, and
92 * AMEND any mappers as needed to accommodate possible changes in the mappings (like representing
93 * some things as strings vs. BLOBs etc).
94 */
95 const ObjectVariantMapper kDBObjectMapper_{[] () {
97 mapper += Employee::kMapper;
98 mapper += Paycheck::kMapper;
99 return mapper;
100 }()};
101
102 /*
103 * Define the schema, and how to map between the VariantValue objects and the database
104 * for the EMPLOYEES table.
105 */
106 const Schema::Table kEmployeesTableSchema_{
107 "EMPLOYEES"sv,
108 /*
109 * use the same names as the ObjectVariantMapper for simpler mapping, or specify an alternate name
110 * for ID, just as an example.
111 */
112 // clang-format off
114 {.fName = "ID"sv, .fVariantValueName = "id"sv, .fRequired = true, .fVariantValueType = VariantValue::eInteger, .fIsKeyField = true, .fDefaultExpression = Schema::Field::kDefaultExpression_AutoIncrement}
115 , {.fName = "NAME"sv, .fVariantValueName = "Name"sv, .fVariantValueType = VariantValue::eString}
116 , {.fName = "AGE"sv, .fVariantValueName = "Age"sv, .fVariantValueType = VariantValue::eInteger}, {.fName = "ADDRESS"sv, .fVariantValueName = "Address"sv, .fVariantValueType = VariantValue::eString}
117 , {.fName = "SALARY"sv, .fVariantValueName = "Salary"sv, .fVariantValueType = VariantValue::eFloat}, {.fName = "STILL_EMPLOYED"sv, .fVariantValueName = "Still-Employed"sv, .fVariantValueType = VariantValue::eInteger}},
118 Schema::CatchAllField{}};
119 // clang-format on
120
121 /*
122 * Define the schema, and how to map between the VariantValue objects and the database
123 * for the PAYCHECKS table.
124 */
125 const Schema::Table kPaychecksTableSchema_{
126 L"PAYCHECKS"sv,
128 // clang-format off
129 {.fName = "ID"sv, .fVariantValueName = "id"sv, .fRequired = true, .fVariantValueType = VariantValue::eInteger, .fIsKeyField = true, .fDefaultExpression = Schema::Field::kDefaultExpression_AutoIncrement}
130 , {.fName = "EMPLOYEEREF"sv, .fVariantValueName = "Employee-Ref"sv, .fRequired = true, .fVariantValueType = VariantValue::eInteger}
131 , {.fName = "AMOUNT"sv, .fVariantValueName = "Amount"sv, .fVariantValueType = VariantValue::eFloat}
132 , {.fName = "DATE"sv, .fVariantValueName = "Date"sv, .fVariantValueType = VariantValue::eDate}}};
133 // clang-format on
134
135 /*
136 * Example thread making updates to the employees table.
137 */
138 void PeriodicallyUpdateEmployeesTable_ (Connection::Ptr conn)
139 {
140 TraceContextBumper ctx{"{}::PeriodicallyUpdateEmployeesTable_"};
141
142 auto employeeTableConnection = make_unique<SQL::ORM::TableConnection<Employee>> (conn, kEmployeesTableSchema_, kDBObjectMapper_);
143
144 // Add Initial Employees
145 employeeTableConnection->AddNew (Employee{.fName = "Paul", .fAge = 32, .fAddress = "California", .fSalary = 20000.00, .fStillEmployed = true});
146 employeeTableConnection->AddNew (Employee{.fName = "Allen", .fAge = 25, .fAddress = "Texas", .fSalary = 15000.00, .fStillEmployed = true});
147 employeeTableConnection->AddNew (Employee{.fName = "Teddy", .fAge = 23, .fAddress = "Norway", .fSalary = 20000.00, .fStillEmployed = true});
148 employeeTableConnection->AddNew (Employee{.fName = "Mark", .fAge = 25, .fAddress = "Rich-Mond", .fSalary = 65000.00, .fStillEmployed = true});
149 employeeTableConnection->AddNew (Employee{.fName = "David", .fAge = 27, .fAddress = "Texas", .fSalary = 85000.00, .fStillEmployed = true});
150 employeeTableConnection->AddNew (Employee{.fName = "Kim", .fAge = 22, .fAddress = "South-Hall", .fSalary = 45000.00, .fStillEmployed = true});
151 employeeTableConnection->AddNew (Employee{.fName = "James", .fAge = 24, .fAddress = "Houston", .fSalary = 10000.00, .fStillEmployed = true});
152
153 default_random_engine generator;
154 uniform_int_distribution<int> distribution{1, 6};
155
156 // then keep adding/removing people randomly (but dont really remove just mark no longer employed so we
157 // can REF in paycheck table
158 while (true) {
159 static const Sequence<String> kNames_{"Joe", "Phred", "Barny", "Sue", "Anne"};
160 uniform_int_distribution<int> namesDistr{0, static_cast<int> (kNames_.size () - 1)};
161 uniform_int_distribution<int> ageDistr{25, 50};
162 static const Sequence<String> kAddresses{"Houston", "Pittsburg", "New York", "Paris", "California"};
163 uniform_int_distribution<int> addressesDistr{0, static_cast<int> (kAddresses.size () - 1)};
164 uniform_real_distribution<float> salaryDistr{10000.00, 50000.00};
165
166 try {
167 uniform_int_distribution<int> whatTodoDistr{0, 3};
168 switch (whatTodoDistr (generator)) {
169 case 0:
170 case 1: {
171 String name = kNames_[namesDistr (generator)];
172 cout << "Adding employee {}"_f(name) << endl;
173 employeeTableConnection->AddNew (Employee{nullopt, name, ageDistr (generator),
174 kAddresses[addressesDistr (generator)], salaryDistr (generator), true});
175 } break;
176 case 2: {
177 // Look somebody up, and fire them
178 auto activeEmps = employeeTableConnection->GetAll ();
179 if (not activeEmps.empty ()) {
180 uniform_int_distribution<int> empDistr{0, static_cast<int> (activeEmps.size () - 1)};
181 Employee killMe = activeEmps[empDistr (generator)];
182 Assert (killMe.ID.has_value ());
183 cout << "Firing employee: {}, {}"_f(*killMe.ID, killMe.fName) << endl;
184 killMe.fStillEmployed = false;
185 employeeTableConnection->Update (killMe);
186 }
187 } break;
188 }
189 }
190 catch (...) {
191 // no need to check for ThreadAbort exception, since Sleep is a cancelation point
192 cout << "Exception processing SQL - this should generally not happen: {}"_f(current_exception ()) << endl;
193 }
194
195 Sleep (1s); // **cancelation point**
196 }
197 }
198
199 /*
200 * Example thread making updates to the paychecks table (while consulting the employees table).
201 */
202 void PeriodicallyWriteChecksForEmployeesTable_ (Connection::Ptr conn)
203 {
204 TraceContextBumper ctx{"{}::PeriodicallyWriteChecksForEmployeesTable_"};
205 auto employeeTableConnection = make_unique<SQL::ORM::TableConnection<Employee>> (conn, kEmployeesTableSchema_, kDBObjectMapper_);
206 auto paycheckTableConnection = make_unique<SQL::ORM::TableConnection<Paycheck>> (conn, kPaychecksTableSchema_, kDBObjectMapper_);
207 while (true) {
208 try {
209 for (const auto& employee : employeeTableConnection->GetAll ()) {
210 Assert (employee.ID != nullopt);
211 cout << "Writing paycheck for employee #{} ({}) amount {}"_f(*employee.ID, employee.fName, employee.fSalary) << endl;
212 paycheckTableConnection->AddNew (Paycheck{nullopt, *employee.ID, employee.fSalary / 12, DateTime::Now ().GetDate ()});
213 }
214 }
215 catch (...) {
216 // no need to check for ThreadAbort excepton, since Sleep is a cancelation point
217 cout << "Exception processing SQL - this should generally not happen: {}"_f(current_exception ()) << endl;
218 }
219 Sleep (2s); // **cancelation point**
220 }
221 }
222}
223
224void Stroika::Samples::SQL::ORMEmployeesDB (const std::function<Connection::Ptr ()>& connectionFactory)
225{
226 TraceContextBumper ctx{"ORMEmployeesDB"};
227
228 Connection::Ptr conn1 = connectionFactory ();
229 Connection::Ptr conn2 = connectionFactory ();
230
231 // setup DB schema (on either connection) before running access threads
232 constexpr Common::Version kCurrentVersion_ = Common::Version{1, 0, Common::VersionStage::Alpha, 0};
233 ORM::ProvisionForVersion (conn1, kCurrentVersion_, Traversal::Iterable<ORM::Schema::Table>{kEmployeesTableSchema_, kPaychecksTableSchema_});
234
235 /*
236 * Create threads for each of our activities.
237 * When the waitable even times out, the threads will automatically be 'canceled' as they go out of scope.
238 */
239 Thread::CleanupPtr updateEmpDBThread{Thread::CleanupPtr::eAbortBeforeWaiting,
240 Thread::New ([=] () { PeriodicallyUpdateEmployeesTable_ (conn1); }, Thread::eAutoStart, "Update Employee Table"sv)};
241 Thread::CleanupPtr writeChecks{Thread::CleanupPtr::eAbortBeforeWaiting,
242 Thread::New ([=] () { PeriodicallyWriteChecksForEmployeesTable_ (conn2); }, Thread::eAutoStart, "Write Checks"sv)};
244}
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
A generalization of a vector: a container whose elements are keyed by the natural numbers.
ObjectVariantMapper can be used to map C++ types to and from variant-union types, which can be transp...
nonvirtual void AddClass(const Traversal::Iterable< StructFieldInfo > &fieldDescriptions, const ClassMapperOptions< CLASS > &mapperOptions={})
nonvirtual void AddCommonType(ARGS &&... args)
nonvirtual WaitStatus WaitQuietly(Time::DurationSeconds timeout=Time::kInfinity)
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:237
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:955
void Sleep(Time::Duration seconds2Wait)
Definition Sleep.cpp:18
STL namespace.