1
0
Fork 0

Basic demonstrator

Works fine but can be slow, especially because of heavy IO.
This commit is contained in:
Teo-CD 2021-10-03 19:42:28 +01:00
commit 8f768ff9e0
8 changed files with 414 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
cmake-*/
**/.idea/

20
CMakeLists.txt Normal file
View file

@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.18)
project(AutoPointsPermis)
set(CMAKE_CXX_STANDARD 20)
set(CommonFiles
db_info.h
LicenceOperations.cpp
LicenceOperations.h)
add_executable(AutoPointsPermis-CreateDb
create.cpp
${CommonFiles})
add_executable(AutoPointsPermis
main.cpp
${CommonFiles})
target_link_libraries(AutoPointsPermis sqlite3)
target_link_libraries(AutoPointsPermis-CreateDb sqlite3)

153
LicenceOperations.cpp Normal file
View file

@ -0,0 +1,153 @@
//
// Created by trotfunky on 02/10/2021.
//
#include "LicenceOperations.h"
void dbError(const std::string& errorMessage, char* dbError) {
std::cerr << errorMessage << " : " << std::endl << dbError << std::endl;
sqlite3_free(dbError);
}
int addNewLicence(sqlite3* db, const std::string& licenceID) {
// Shift the hash two places down to fit properly in an SQLite integer
std::string idHash = std::to_string(std::hash<std::string>{}(licenceID) >> 2);
char* errorMessage = nullptr;
int errorCode;
errorCode = sqlite3_exec(db, ("INSERT INTO " + pointsTable + " VALUES (" + idHash
+ ", '" + licenceID + "', 12, 0, 0, 0, 0);").c_str(),
nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error adding new licence with ID " + licenceID, errorMessage);
}
return errorCode;
}
int applyOffence(sqlite3* db, const std::string& licenceID, int lostPoints, offenceClass offenceClass) {
// Shift the hash two places down to fit properly in an SQLite integer
std::string idHash = std::to_string(std::hash<std::string>{}(licenceID) >> 2);
char* errorMessage = nullptr;
int errorCode;
char** licenceInfo = nullptr;
// Retrieve points and grave offence info, that's all we need.
errorCode = sqlite3_get_table(db, ("SELECT " + pointsColumn + "," + graveOffenceColumn + ", " + onePointCountdownColumn
+ " FROM " + pointsTable + " WHERE " + keyColumn + " = " + idHash + ";").c_str(),
&licenceInfo, nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error getting info for licence " + licenceID, errorMessage);
return errorCode;
}
int points = std::stoi(licenceInfo[3]);
bool hasGraveOffence = std::stoi(licenceInfo[4]);
// Can only change from false to true for offences of class 4 or 5
bool newGraveOffence = offenceClass > offenceClass::Three && !hasGraveOffence;
int newTenYearCountdown = -1;
// Only set for the first new offence when at maximum points, clears if class five offence
if (points == 12 && offenceClass < offenceClass::Five) {
newTenYearCountdown = 3653;
} else if (offenceClass == offenceClass::Five) {
newTenYearCountdown = 0;
}
int newCountdown;
int onePointCountdown = std::stoi(licenceInfo[5]);
int newOnePointCountdown = -1;
// This minimizes affected rows for one point offences when it's the first offence at maximum points.
// Not sure if that's useful.
if (lostPoints == 1 && points == 12) {
newCountdown = 183;
} else {
newCountdown = (2 + (hasGraveOffence || newGraveOffence)) * 365;
if (lostPoints == 1) {
newOnePointCountdown = 183;
} else if (onePointCountdown) {
newOnePointCountdown = 0;
}
}
points -= lostPoints;
// Licence is revoked, reset everything.
if (points <= 0) {
points = 0;
newCountdown = 0;
newOnePointCountdown = 0;
newTenYearCountdown = 0;
}
errorCode = sqlite3_exec(
db, ("UPDATE " + pointsTable + " SET "
+ pointsColumn + " = " + std::to_string(points) + ", "
+ countdownColumn + " = " + std::to_string(newCountdown)
+ (newOnePointCountdown >= 0 ? ", " + onePointCountdownColumn + " = " + std::to_string(newOnePointCountdown) : "")
+ (newTenYearCountdown >= 0 ? ", " + tenYearsCountdownColumn + " = " + std::to_string(newTenYearCountdown) : "")
+ (newGraveOffence ? ", " + graveOffenceColumn + " = 1" : "")
+ " WHERE " + keyColumn + " = " + idHash + ";").c_str(),
nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error updating licence " + licenceID, errorMessage);
}
return errorCode;
}
int countdown(void* db, int columnCount, char** rowData, char** columnNames) {
char* errorMessage = nullptr;
int errorCode;
int points = std::stoi(rowData[1]);
int newPoints = -1;
int countdown = std::stoi(rowData[2]);
int onePointCoutdown = std::stoi(rowData[3]);
int tenYearCountdown = std::stoi(rowData[4]);
if (tenYearCountdown == 1 || countdown == 1) {
// If resetting the counters, don't do anything else
return reset((sqlite3*)db, rowData[0]);
} else if (onePointCoutdown == 1) {
newPoints = points + 1;
}
countdown -= 1;
onePointCoutdown -= 1;
tenYearCountdown -= 1;
errorCode = sqlite3_exec(
(sqlite3*)db, ("UPDATE " + pointsTable + " SET "
+ countdownColumn + " = " + std::to_string(countdown)
+ (newPoints >= 0 ? ", " + pointsColumn + " = " + std::to_string(newPoints) : "")
+ (onePointCoutdown >= 0 ? ", " + onePointCountdownColumn + " = " + std::to_string(onePointCoutdown) : "")
+ (tenYearCountdown >= 0 ? ", " + tenYearsCountdownColumn + " = " + std::to_string(tenYearCountdown) : "")
+ " WHERE " + keyColumn + " = " + rowData[0]).c_str(),
nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error while updating coutdowns for licence hash " + std::string(rowData[0]), errorMessage);
}
return errorCode;
}
int reset(sqlite3* db, const std::string& idHash) {
char* errorMessage = nullptr;
int errorCode;
errorCode = sqlite3_exec(db, ("UPDATE " + pointsTable + " SET "
+ pointsColumn + " = 12, "
+ countdownColumn + " = 0,"
+ onePointCountdownColumn + " = 0,"
+ tenYearsCountdownColumn + " = 0,"
+ " WHERE " + keyColumn + " = " + idHash).c_str(),
nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error resetting licence " + idHash, errorMessage);
}
return errorCode;
}

36
LicenceOperations.h Normal file
View file

@ -0,0 +1,36 @@
//
// Created by trotfunky on 02/10/2021.
//
#ifndef AUTOPOINTSPERMIS_LICENCEOPERATIONS_H
#define AUTOPOINTSPERMIS_LICENCEOPERATIONS_H
#include <functional>
#include <iostream>
#include <sqlite3.h>
#include <string>
#include "db_info.h"
// Assign values directly to prevent starting from 0 without using a dummy value
enum class offenceClass {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5
};
void dbError(const std::string& errorMessage, char* dbError);
/// Adds a new licence with 12 points and no counters running.
int addNewLicence(sqlite3* db, const std::string& licenceID);
/// Apply a new offence to an existing licence.
int applyOffence(sqlite3* db, const std::string& licenceID, int lostPoints, offenceClass offenceClass);
/// Apply the countdowns, check for completion and add points back.
/// Fetched columns : idHash, Points, Countdown, OnePointCoutdown, TenYearCountdown
int countdown(void* db, int columnCount, char** rowData, char** columnNames);
/// Resets all counters and maximum points for an existing licence
int reset(sqlite3* db, const std::string& idHash);
#endif //AUTOPOINTSPERMIS_LICENCEOPERATIONS_H

26
README.md Normal file
View file

@ -0,0 +1,26 @@
# AutoPointsPermis
A silly project recreating what would be needed to handle the basics
of French driver licence point handling
# Usage
This project builds two executables. One creates and fills the database
with licences and offences, the other is there to simulate daily checks
to see if points need to be awarded back.
# Building
## Dependencies
- Sqlite3
# Improvements
Currently, everything uses very naive SQL querying, with the main optimisation
being batching. There is certainly smarter ways to go about it and some ways
to make it run faster.
The daily program could be improved to be a server of sorts, allowing
the manipulation of the database in a controlled fashion. (Telnet ?)
Maybe test with another DB ?

97
create.cpp Normal file
View file

@ -0,0 +1,97 @@
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sqlite3.h>
#include <string>
#include <sstream>
#include "db_info.h"
#include "LicenceOperations.h"
constexpr int totalLicences = 50'000'000;
constexpr float offenceRate = 0.2;
/// Generate a random string of numbers and letters
/// ASCII letters are contiguous so juste add a random offset to 'a'.
std::string createRandomID() {
char newID[IDLength];
for (char& idChar : newID) {
if (rand() % 2) {
idChar = 'a' + rand() % 26;
} else {
idChar = '0' + rand() % 10;
}
}
return newID;
}
int main()
{
srand(time(nullptr));
sqlite3* db;
char* errorMessage = nullptr;
int errorCode;
errorCode = sqlite3_open(dbName.c_str(), &db);
// errorCode = sqlite3_open("TestDB.sqlite3", &db);
if (errorCode) {
std::cerr << "Error opening database" << std::endl << sqlite3_errmsg(db) << std::endl;
sqlite3_close(db);
return -1;
}
errorCode = sqlite3_exec(db, ("CREATE TABLE IF NOT EXISTS " + pointsTable + "("
+ keyColumn + " UNSIGNED BIG INT PRIMARY KEY NOT NULL,"
+ idColumn + " TEXT NOT NULL,"
+ pointsColumn + " INT NOT NULL,"
+ countdownColumn + " INT NOT NULL,"
+ onePointCountdownColumn + " INT NOT NULL,"
+ tenYearsCountdownColumn + " INT NOT NULL,"
+ graveOffenceColumn + " INT NOT NULL);").c_str(),
nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error creating table " + pointsTable, errorMessage);
sqlite3_close(db);
return -1;
}
// Batch all inits
errorCode = sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error while beginning transaction", errorMessage);
sqlite3_close(db);
return -1;
}
std::ios::sync_with_stdio(false);
std::time_t startTime = std::time(nullptr);
for (int i = 0; i < totalLicences; i++) {
const std::string& ID = createRandomID();
errorCode = addNewLicence(db, ID);
if (rand() < RAND_MAX * offenceRate) {
applyOffence(db, ID, rand() % 6 + 1, (offenceClass)(rand() % 5 + 1));
}
if (!(i % 10000)) {
std::cout << "Created " << i << " licences" << std::endl;
}
if (errorCode) {
break;
}
}
errorCode = sqlite3_exec(db, "END TRANSACTION;", nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error while closing transaction", errorMessage);
sqlite3_close(db);
return -1;
}
std::cout << "Db created successfully !" << std::endl;
std::cout << totalLicences << " licences created in " << std::time(nullptr) - startTime << std::endl;
sqlite3_close(db);
return 0;
}

23
db_info.h Normal file
View file

@ -0,0 +1,23 @@
//
// Created by trotfunky on 02/10/2021.
//
#ifndef AUTOPOINTSPERMIS_DB_INFO_H
#define AUTOPOINTSPERMIS_DB_INFO_H
#include <string>
const std::string dbName = "Permis.sqlite3";
const std::string pointsTable = "POINTS";
const std::string keyColumn = "LicenseHash";
const std::string idColumn = "LicenseID";
const std::string pointsColumn = "Points";
const std::string countdownColumn = "Countdown";
const std::string onePointCountdownColumn = "OneCountdown";
const std::string tenYearsCountdownColumn = "TenYCountdown";
const std::string graveOffenceColumn = "HasGrave";
constexpr int IDLength = 40;
#endif //AUTOPOINTSPERMIS_DB_INFO_H

56
main.cpp Normal file
View file

@ -0,0 +1,56 @@
#include <iostream>
#include <sqlite3.h>
#include <ctime>
#include "db_info.h"
#include "LicenceOperations.h"
int main()
{
char* errorMessage = nullptr;
int errorCode;
sqlite3* db = nullptr;
if (sqlite3_open(dbName.c_str(), &db)) {
std::cerr << "Error opening database : " << std::endl << sqlite3_errmsg(db) << std::endl;
sqlite3_close(db);
return -1;
}
std::time_t startTime = std::time(nullptr);
errorCode = sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error while beginning transaction", errorMessage);
sqlite3_close(db);
return -1;
}
errorCode = sqlite3_exec(db, ("SELECT "
+ keyColumn + ", "
+ pointsColumn + ", "
+ countdownColumn + ", "
+ onePointCountdownColumn + ", "
+ tenYearsCountdownColumn
+ " FROM " + pointsTable
+ " WHERE " + pointsColumn + " > 0 AND " + pointsColumn + " < 12;").c_str(),
countdown, db, &errorMessage);
if (errorCode) {
dbError("Error while running coutdowns", errorMessage);
sqlite3_close(db);
return -1;
}
errorCode = sqlite3_exec(db, "END TRANSACTION;", nullptr, nullptr, &errorMessage);
if (errorCode) {
dbError("Error while closing transaction", errorMessage);
sqlite3_close(db);
return -1;
}
std::cout << "Successfully updated database in " << std::time(nullptr) - startTime << "s" << std::endl;
sqlite3_close(db);
return 0;
}