sqlce 0.1.2__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {sqlce-0.1.2 → sqlce-0.1.3}/.gitignore +1 -0
  2. sqlce-0.1.3/PKG-INFO +87 -0
  3. sqlce-0.1.3/README.md +56 -0
  4. sqlce-0.1.3/core/include/sdf/domain/DateTimeValue.hpp +36 -0
  5. sqlce-0.1.3/core/src/domain/DateTimeValue.cpp +135 -0
  6. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/RowDecoder.cpp +4 -4
  7. {sqlce-0.1.2 → sqlce-0.1.3}/docs/build.txt +2 -2
  8. sqlce-0.1.2/PKG-INFO → sqlce-0.1.3/docs/ru/RuReadme.md +28 -36
  9. {sqlce-0.1.2 → sqlce-0.1.3}/pyproject.toml +7 -1
  10. {sqlce-0.1.2 → sqlce-0.1.3}/requirements.txt +2 -0
  11. sqlce-0.1.3/tests/__init__.py +0 -0
  12. sqlce-0.1.3/tests/conftest.py +80 -0
  13. sqlce-0.1.3/tests/edge_case_tables.py +209 -0
  14. sqlce-0.1.3/tests/sample_tables.py +15 -0
  15. sqlce-0.1.3/tests/sdf_factory.py +253 -0
  16. sqlce-0.1.3/tests/table_spec.py +170 -0
  17. sqlce-0.1.3/tests/test_sdf40_edge_cases.py +120 -0
  18. sqlce-0.1.3/tests/test_sdf40_encrypted_engine_default.py +49 -0
  19. sqlce-0.1.3/tests/test_sdf40_encrypted_platform_default.py +49 -0
  20. sqlce-0.1.3/tests/test_sdf40_plain.py +37 -0
  21. sqlce-0.1.2/README.md +0 -37
  22. sqlce-0.1.2/core/include/sdf/domain/DateTimeValue.hpp +0 -36
  23. sqlce-0.1.2/core/src/domain/DateTimeValue.cpp +0 -107
  24. {sqlce-0.1.2 → sqlce-0.1.3}/.github/workflows/release.yml +0 -0
  25. {sqlce-0.1.2 → sqlce-0.1.3}/CMakeLists.txt +0 -0
  26. {sqlce-0.1.2 → sqlce-0.1.3}/LICENSE +0 -0
  27. {sqlce-0.1.2 → sqlce-0.1.3}/bindings/CMakeLists.txt +0 -0
  28. {sqlce-0.1.2 → sqlce-0.1.3}/bindings/src/module.cpp +0 -0
  29. {sqlce-0.1.2 → sqlce-0.1.3}/bindings/src/value_convert.cpp +0 -0
  30. {sqlce-0.1.2 → sqlce-0.1.3}/bindings/src/value_convert.hpp +0 -0
  31. {sqlce-0.1.2 → sqlce-0.1.3}/core/CMakeLists.txt +0 -0
  32. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/application/ColumnSchema.hpp +0 -0
  33. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/application/SdfDatabase.hpp +0 -0
  34. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/ColumnDef.hpp +0 -0
  35. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/ColumnType.hpp +0 -0
  36. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/ColumnValue.hpp +0 -0
  37. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/EncryptionMode.hpp +0 -0
  38. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/Guid.hpp +0 -0
  39. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/IPageCipher.hpp +0 -0
  40. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/IPageStorage.hpp +0 -0
  41. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/NumericValue.hpp +0 -0
  42. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/PageLayout.hpp +0 -0
  43. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/Row.hpp +0 -0
  44. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/TableDef.hpp +0 -0
  45. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/AesCbc.hpp +0 -0
  46. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/BinaryReader.hpp +0 -0
  47. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/FileStorage.hpp +0 -0
  48. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/PageView.hpp +0 -0
  49. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/Sha256.hpp +0 -0
  50. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/Sha512.hpp +0 -0
  51. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogPageScanner.hpp +0 -0
  52. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogRow.hpp +0 -0
  53. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogRowDecoder.hpp +0 -0
  54. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogSchema.hpp +0 -0
  55. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ICatalogPageScanner.hpp +0 -0
  56. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ICatalogRowDecoder.hpp +0 -0
  57. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ILobChainRegistry.hpp +0 -0
  58. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/IRowDecoder.hpp +0 -0
  59. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ITableCatalogBuilder.hpp +0 -0
  60. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/LobChainRegistry.hpp +0 -0
  61. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/RowDecoder.hpp +0 -0
  62. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/SdfPageCipher.hpp +0 -0
  63. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/TableCatalogBuilder.hpp +0 -0
  64. {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/TextDecoder.hpp +0 -0
  65. {sqlce-0.1.2 → sqlce-0.1.3}/core/main.cpp +0 -0
  66. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/application/SdfDatabase.cpp +0 -0
  67. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/ColumnDef.cpp +0 -0
  68. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/ColumnType.cpp +0 -0
  69. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/ColumnValue.cpp +0 -0
  70. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/Guid.cpp +0 -0
  71. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/NumericValue.cpp +0 -0
  72. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/Row.cpp +0 -0
  73. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/TableDef.cpp +0 -0
  74. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/AesCbc.cpp +0 -0
  75. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/BinaryReader.cpp +0 -0
  76. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/FileStorage.cpp +0 -0
  77. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/PageView.cpp +0 -0
  78. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/Sha256.cpp +0 -0
  79. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/Sha512.cpp +0 -0
  80. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogPageScanner.cpp +0 -0
  81. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogRow.cpp +0 -0
  82. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogRowDecoder.cpp +0 -0
  83. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogSchema.cpp +0 -0
  84. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/LobChainRegistry.cpp +0 -0
  85. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/SdfPageCipher.cpp +0 -0
  86. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/TableCatalogBuilder.cpp +0 -0
  87. {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/TextDecoder.cpp +0 -0
  88. {sqlce-0.1.2 → sqlce-0.1.3}/core/tests/CMakeLists.txt +0 -0
  89. {sqlce-0.1.2 → sqlce-0.1.3}/core/tests/count_test.cpp +0 -0
  90. {sqlce-0.1.2 → sqlce-0.1.3}/docs/CONVENTIONS.md +0 -0
  91. {sqlce-0.1.2 → sqlce-0.1.3}/docs/en/cpp.md +0 -0
  92. {sqlce-0.1.2 → sqlce-0.1.3}/docs/en/python.md +0 -0
  93. {sqlce-0.1.2 → sqlce-0.1.3}/docs/ru/cpp.md +0 -0
  94. {sqlce-0.1.2 → sqlce-0.1.3}/docs/ru/python.md +0 -0
  95. {sqlce-0.1.2 → sqlce-0.1.3}/main.py +0 -0
  96. {sqlce-0.1.2 → sqlce-0.1.3}/python/sqlce/__init__.py +0 -0
  97. {sqlce-0.1.2 → sqlce-0.1.3}/python/sqlce/__init__.pyi +0 -0
  98. {sqlce-0.1.2 → sqlce-0.1.3}/python/sqlce/_sdf_native.pyi +0 -0
  99. {sqlce-0.1.2 → sqlce-0.1.3}/research/catalog_parse/sdf_pages.py +0 -0
  100. {sqlce-0.1.2 → sqlce-0.1.3}/research/catalog_parse/syscatalog.py +0 -0
  101. {sqlce-0.1.2 → sqlce-0.1.3}/research/sdf_d3.py +0 -0
  102. {sqlce-0.1.2 → sqlce-0.1.3}/research/sdf_d4.py +0 -0
  103. {sqlce-0.1.2 → sqlce-0.1.3}/research/sdf_reader.py +0 -0
@@ -41,6 +41,7 @@ install_manifest.txt
41
41
  compile_commands.json
42
42
  vcpkg_installed/
43
43
  Testing/
44
+ tests/sdf/
44
45
  .cache/
45
46
 
46
47
  .idea/
sqlce-0.1.3/PKG-INFO ADDED
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.2
2
+ Name: sqlce
3
+ Version: 0.1.3
4
+ Summary: Cross-platform lib for reading SQL Server CE (.sdf)
5
+ Author-Email: boykopovar <boykopovar@gmail.com>
6
+ License: MIT
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: C++
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Project-URL: Homepage, https://github.com/boykopovar/sqlce
20
+ Project-URL: Issues, https://github.com/boykopovar/sqlce/issues
21
+ Requires-Python: >=3.8
22
+ Provides-Extra: dev
23
+ Requires-Dist: scikit-build-core>=0.10; extra == "dev"
24
+ Requires-Dist: pybind11>=2.12; extra == "dev"
25
+ Requires-Dist: build; extra == "dev"
26
+ Requires-Dist: twine; extra == "dev"
27
+ Requires-Dist: pytest; extra == "dev"
28
+ Requires-Dist: pytest-cov; extra == "dev"
29
+ Requires-Dist: pythonnet; extra == "dev"
30
+ Description-Content-Type: text/markdown
31
+
32
+ # sqlce
33
+
34
+ ![C++](https://img.shields.io/badge/C%2B%2B-20-blue?logo=cplusplus&logoColor=white)
35
+ ![Python](https://img.shields.io/badge/python-3.8%2B-blue?logo=python&logoColor=white)
36
+ ![Platform](https://img.shields.io/badge/platform-cross--platform-lightgrey)
37
+ ![License](https://img.shields.io/badge/license-MIT-green)
38
+ ![Reverse engineered](https://img.shields.io/badge/Reverse%20engineered%20with-🔬-red)
39
+
40
+ ## [Ru README](docs/ru/RuReadme.md)
41
+
42
+ Reverse engineering of the `.sdf` (SQL Server Compact Edition 4.0) binary file format.
43
+
44
+ Analysis of the format's internal structure based on decompiled SQL CE components. The goal is to provide a cross-platform, runtime-independent implementation for reading `.sdf` files.
45
+
46
+ > Intended solely for interoperability with existing `.sdf` files. Does not contain any original product source code.
47
+
48
+ - `core/` — C++ parser implementation
49
+ - `bindings/` — Bindings for the C++ library to other languages
50
+ - `python/sqlce/` — Python API
51
+ - `research/` — Experimental scripts
52
+
53
+ ### Example
54
+
55
+ ```bash
56
+ pip install sqlce
57
+ ```
58
+
59
+ ```python
60
+ from sqlce import SdfDatabase
61
+
62
+ db = SdfDatabase("example.sdf", password="secret123")
63
+
64
+ for table_name in db.list_tables():
65
+ print(table_name, db.table_schema(table_name))
66
+ for row in db.read_table(table_name):
67
+ print(row)
68
+ ```
69
+
70
+ The current implementation supports reading the list of tables, table schemas, and table contents (including password-protected databases). It is implemented in C++.
71
+
72
+ ### Testing
73
+
74
+ ```bash
75
+ pytest
76
+ ```
77
+
78
+ The tests use a locally installed SQL CE engine (via ODBC) to generate `.sdf` files containing predefined data and verify that the parser produces identical results. Therefore, the test suite can only be run on Windows with SQL Server Compact 4.0 installed. Test files are created in `tests/sdf/` and are automatically removed before and after each test run.
79
+
80
+ ### Documentation
81
+
82
+ The documentation is currently concise but includes usage examples and tests.
83
+
84
+ [Documentation](docs/en/python.md)
85
+
86
+
87
+ [Code Style Conventions](docs/CONVENTIONS.md) (Pull requests are welcome.)
sqlce-0.1.3/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # sqlce
2
+
3
+ ![C++](https://img.shields.io/badge/C%2B%2B-20-blue?logo=cplusplus&logoColor=white)
4
+ ![Python](https://img.shields.io/badge/python-3.8%2B-blue?logo=python&logoColor=white)
5
+ ![Platform](https://img.shields.io/badge/platform-cross--platform-lightgrey)
6
+ ![License](https://img.shields.io/badge/license-MIT-green)
7
+ ![Reverse engineered](https://img.shields.io/badge/Reverse%20engineered%20with-🔬-red)
8
+
9
+ ## [Ru README](docs/ru/RuReadme.md)
10
+
11
+ Reverse engineering of the `.sdf` (SQL Server Compact Edition 4.0) binary file format.
12
+
13
+ Analysis of the format's internal structure based on decompiled SQL CE components. The goal is to provide a cross-platform, runtime-independent implementation for reading `.sdf` files.
14
+
15
+ > Intended solely for interoperability with existing `.sdf` files. Does not contain any original product source code.
16
+
17
+ - `core/` — C++ parser implementation
18
+ - `bindings/` — Bindings for the C++ library to other languages
19
+ - `python/sqlce/` — Python API
20
+ - `research/` — Experimental scripts
21
+
22
+ ### Example
23
+
24
+ ```bash
25
+ pip install sqlce
26
+ ```
27
+
28
+ ```python
29
+ from sqlce import SdfDatabase
30
+
31
+ db = SdfDatabase("example.sdf", password="secret123")
32
+
33
+ for table_name in db.list_tables():
34
+ print(table_name, db.table_schema(table_name))
35
+ for row in db.read_table(table_name):
36
+ print(row)
37
+ ```
38
+
39
+ The current implementation supports reading the list of tables, table schemas, and table contents (including password-protected databases). It is implemented in C++.
40
+
41
+ ### Testing
42
+
43
+ ```bash
44
+ pytest
45
+ ```
46
+
47
+ The tests use a locally installed SQL CE engine (via ODBC) to generate `.sdf` files containing predefined data and verify that the parser produces identical results. Therefore, the test suite can only be run on Windows with SQL Server Compact 4.0 installed. Test files are created in `tests/sdf/` and are automatically removed before and after each test run.
48
+
49
+ ### Documentation
50
+
51
+ The documentation is currently concise but includes usage examples and tests.
52
+
53
+ [Documentation](docs/en/python.md)
54
+
55
+
56
+ [Code Style Conventions](docs/CONVENTIONS.md) (Pull requests are welcome.)
@@ -0,0 +1,36 @@
1
+ #ifndef SDF_DOMAIN_DATE_TIME_VALUE_HPP
2
+ #define SDF_DOMAIN_DATE_TIME_VALUE_HPP
3
+
4
+ #include <cstdint>
5
+ #include <string>
6
+
7
+ namespace sdf::domain
8
+ {
9
+
10
+ struct CalendarDate
11
+ {
12
+ int year;
13
+ int month;
14
+ int day;
15
+ };
16
+
17
+ class DateTimeValue
18
+ {
19
+ public:
20
+ DateTimeValue();
21
+ DateTimeValue(std::int32_t daysSinceEpoch, std::uint32_t ticksSinceMidnight);
22
+
23
+ std::uint32_t MillisecondsSinceMidnight() const;
24
+ std::int32_t DaysSinceEpoch() const;
25
+ CalendarDate Date() const;
26
+
27
+ std::string ToString() const;
28
+
29
+ private:
30
+ std::int32_t _daysSinceEpoch;
31
+ std::uint32_t _ticksSinceMidnight;
32
+ };
33
+
34
+ }
35
+
36
+ #endif
@@ -0,0 +1,135 @@
1
+ #include "sdf/domain/DateTimeValue.hpp"
2
+
3
+ #include <cstdio>
4
+
5
+ namespace sdf::domain
6
+ {
7
+
8
+ namespace
9
+ {
10
+
11
+ constexpr int EpochYear = 1900;
12
+ constexpr int EpochMonth = 1;
13
+ constexpr int EpochDay = 1;
14
+
15
+ bool IsLeapYear(int year)
16
+ {
17
+ return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
18
+ }
19
+
20
+ int DaysInMonth(int year, int month)
21
+ {
22
+ static constexpr int daysByMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
23
+ if (month == 2 && IsLeapYear(year))
24
+ {
25
+ return 29;
26
+ }
27
+ return daysByMonth[month - 1];
28
+ }
29
+
30
+ CalendarDate AddDays(int year, int month, int day, std::int32_t daysToAdd)
31
+ {
32
+ if (daysToAdd >= 0)
33
+ {
34
+ std::uint32_t remaining = static_cast<std::uint32_t>(daysToAdd);
35
+ while (remaining > 0)
36
+ {
37
+ const int daysLeftInMonth = DaysInMonth(year, month) - day;
38
+ if (static_cast<std::uint32_t>(daysLeftInMonth) >= remaining)
39
+ {
40
+ day += static_cast<int>(remaining);
41
+ remaining = 0;
42
+ }
43
+ else
44
+ {
45
+ remaining -= static_cast<std::uint32_t>(daysLeftInMonth) + 1;
46
+ day = 1;
47
+ month += 1;
48
+ if (month > 12)
49
+ {
50
+ month = 1;
51
+ year += 1;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ else
57
+ {
58
+ std::uint32_t remaining = static_cast<std::uint32_t>(-daysToAdd);
59
+ while (remaining > 0)
60
+ {
61
+ const int daysIntoMonth = day - 1;
62
+ if (static_cast<std::uint32_t>(daysIntoMonth) >= remaining)
63
+ {
64
+ day -= static_cast<int>(remaining);
65
+ remaining = 0;
66
+ }
67
+ else
68
+ {
69
+ remaining -= static_cast<std::uint32_t>(daysIntoMonth) + 1;
70
+ month -= 1;
71
+ if (month < 1)
72
+ {
73
+ month = 12;
74
+ year -= 1;
75
+ }
76
+ day = DaysInMonth(year, month);
77
+ }
78
+ }
79
+ }
80
+ return CalendarDate{year, month, day};
81
+ }
82
+
83
+ }
84
+
85
+ DateTimeValue::DateTimeValue() : _daysSinceEpoch(0), _ticksSinceMidnight(0)
86
+ {
87
+ }
88
+
89
+ DateTimeValue::DateTimeValue(std::int32_t daysSinceEpoch, std::uint32_t ticksSinceMidnight)
90
+ : _daysSinceEpoch(daysSinceEpoch), _ticksSinceMidnight(ticksSinceMidnight)
91
+ {
92
+ }
93
+
94
+ std::uint32_t DateTimeValue::MillisecondsSinceMidnight() const
95
+ {
96
+ return static_cast<std::uint32_t>((static_cast<std::uint64_t>(_ticksSinceMidnight) * 10ULL + 1ULL) / 3ULL);
97
+ }
98
+
99
+ std::int32_t DateTimeValue::DaysSinceEpoch() const
100
+ {
101
+ return _daysSinceEpoch;
102
+ }
103
+
104
+ CalendarDate DateTimeValue::Date() const
105
+ {
106
+ return AddDays(EpochYear, EpochMonth, EpochDay, _daysSinceEpoch);
107
+ }
108
+
109
+ std::string DateTimeValue::ToString() const
110
+ {
111
+ const CalendarDate date = Date();
112
+
113
+ const std::uint32_t milliseconds = MillisecondsSinceMidnight();
114
+ const std::uint32_t totalSeconds = milliseconds / 1000;
115
+ const std::uint32_t ms = milliseconds % 1000;
116
+ const std::uint32_t hours = (totalSeconds / 3600) % 24;
117
+ const std::uint32_t minutes = (totalSeconds / 60) % 60;
118
+ const std::uint32_t seconds = totalSeconds % 60;
119
+
120
+ char buffer[32];
121
+ std::snprintf(
122
+ buffer,
123
+ sizeof(buffer),
124
+ "%04d-%02d-%02d %02u:%02u:%02u.%03u",
125
+ date.year,
126
+ date.month,
127
+ date.day,
128
+ hours,
129
+ minutes,
130
+ seconds,
131
+ ms);
132
+ return std::string(buffer);
133
+ }
134
+
135
+ }
@@ -232,9 +232,9 @@ domain::ColumnValue RowDecoder::DecodeFixed(const domain::ColumnDef& column, std
232
232
 
233
233
  case domain::ColumnType::DateTime:
234
234
  {
235
- const std::uint32_t milliseconds = infrastructure::ReadUInt32LE(chunk, 0);
236
- const std::uint32_t days = infrastructure::ReadUInt32LE(chunk, 4);
237
- return domain::ColumnValue(domain::ColumnValueStorage(domain::DateTimeValue(milliseconds, days)));
235
+ const std::uint32_t ticks = infrastructure::ReadUInt32LE(chunk, 0);
236
+ const std::int32_t days = infrastructure::ReadInt32LE(chunk, 4);
237
+ return domain::ColumnValue(domain::ColumnValueStorage(domain::DateTimeValue(days, ticks)));
238
238
  }
239
239
 
240
240
  case domain::ColumnType::Money:
@@ -318,4 +318,4 @@ domain::ColumnValue RowDecoder::DecodeLob(
318
318
  return domain::ColumnValue(domain::ColumnValueStorage(payload));
319
319
  }
320
320
 
321
- }
321
+ }
@@ -1,8 +1,8 @@
1
1
  pip install -e . -v --config-settings=cmake.args="-G Ninja;-DCMAKE_CXX_COMPILER=g++;-DCMAKE_C_COMPILER=gcc;-DSDF_BUILD_PYTHON_BINDINGS=ON"
2
2
  python -m build --wheel -C cmake.args="-G Ninja;-DCMAKE_CXX_COMPILER=g++;-DCMAKE_C_COMPILER=gcc;-DSDF_BUILD_PYTHON_BINDINGS=ON"
3
3
 
4
- git tag v0.1.1
5
- git push origin v0.1.1
4
+ git tag v0.1.2
5
+ git push origin v0.1.2
6
6
 
7
7
  git tag -d v0.1.0
8
8
  git push origin --delete v0.1.0
@@ -1,44 +1,26 @@
1
- Metadata-Version: 2.2
2
- Name: sqlce
3
- Version: 0.1.2
4
- Summary: Cross-platform lib for reading SQL Server CE (.sdf)
5
- Author-Email: boykopovar <boykopovar@gmail.com>
6
- License: MIT
7
- Classifier: Development Status :: 4 - Beta
8
- Classifier: License :: OSI Approved :: MIT License
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.8
11
- Classifier: Programming Language :: Python :: 3.9
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: C++
17
- Classifier: Operating System :: Microsoft :: Windows
18
- Classifier: Operating System :: POSIX :: Linux
19
- Project-URL: Homepage, https://github.com/boykopovar/sqlce
20
- Project-URL: Issues, https://github.com/boykopovar/sqlce/issues
21
- Requires-Python: >=3.8
22
- Provides-Extra: dev
23
- Requires-Dist: scikit-build-core>=0.10; extra == "dev"
24
- Requires-Dist: pybind11>=2.12; extra == "dev"
25
- Requires-Dist: build; extra == "dev"
26
- Requires-Dist: twine; extra == "dev"
27
- Requires-Dist: pytest; extra == "dev"
28
- Description-Content-Type: text/markdown
29
-
30
1
  # sqlce
31
2
 
3
+ ![C++](https://img.shields.io/badge/C%2B%2B-20-blue?logo=cplusplus&logoColor=white)
4
+ ![Python](https://img.shields.io/badge/python-3.8%2B-blue?logo=python&logoColor=white)
5
+ ![Platform](https://img.shields.io/badge/platform-cross--platform-lightgrey)
6
+ ![License](https://img.shields.io/badge/license-MIT-green)
7
+ ![Reverse engineered](https://img.shields.io/badge/Reverse%20engineered%20with-🔬-red)
8
+
9
+ ## [En README](../../README.md)
10
+
32
11
  Реверс-инжиниринг бинарного формата `.sdf` (SQL Server Compact Edition 4.0).
33
-
34
- Анализ внутреннего устройства формата по декомпилированным компонентам SQL CE. Цель - предоставить кроссплатформенную и независимую от рантайма реализацию чтения `.sdf` и документацию формата.
12
+
13
+ Анализ внутреннего устройства формата по декомпилированным компонентам SQL CE. Цель - предоставить кроссплатформенную и независимую от рантайма реализацию чтения `.sdf`.
14
+
15
+ > Исключительно для интероперабельности с существующими файлами `.sdf` формата. Не содержит оригинальный код продукта.
35
16
 
36
17
 
37
18
  - `core/` - реализация парсера на C++
38
- - `bindings/` - Биндинги для C++ библиотеки к другим языкам
19
+ - `bindings/` - Биндинги C++ библиотеки к другим языкам
39
20
  - `python/sqlce/` - Python API
40
21
  - `research/` - Экспериментальные скрипты
41
22
 
23
+ ### Пример
42
24
 
43
25
  ```bash
44
26
  pip install sqlce
@@ -57,10 +39,20 @@ for table_name in db.list_tables():
57
39
 
58
40
  Текущая [реализация](main.py) поддерживает чтение (поддерживается передача пароля) списка таблиц, структуры таблиц, содержимого записей. Использует c++.
59
41
 
60
- [Документация](docs/ru/python.md)
42
+ ### Тестирование
43
+
44
+ ```bash
45
+ pytest
46
+ ```
47
+
48
+ Тесты используют установленный на устройстве SQL CE движок (через ODBC) для генерации `.sdf`-файлов с заранее известными данными и сверяют их с результатом чтения реализованным парсером - так что запуск возможен только на Windows с установленным SQL Server Compact 4.0. Файлы создаются в `tests/sdf/` и автоматически удаляются в начале и после каждого прогона.
61
49
 
62
- [Documentation](docs/en/python.md)
63
50
 
64
- [Соглашения по написанию кода](docs/CONVENTIONS.md) (PR приветствуются)
51
+ ### Документация
65
52
 
66
- > Исключительно для интероперабельности с существующими файлами `.sdf` формата. Не содержит оригинальный код продукта.
53
+ На данный момент документация краткая, но есть примеры и тесты.
54
+
55
+ [Документация](python.md)
56
+
57
+
58
+ [Соглашения по написанию кода](docs/CONVENTIONS.md) (PR приветствуются)
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "sqlce"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Cross-platform lib for reading SQL Server CE (.sdf)"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -60,4 +60,10 @@ dev = [
60
60
  "build",
61
61
  "twine",
62
62
  "pytest",
63
+ "pytest-cov",
64
+ "pythonnet"
63
65
  ]
66
+
67
+ [tool.pytest.ini_options]
68
+ addopts = "--cov=sqlce --cov-report=term-missing"
69
+ testpaths = ["tests"]
@@ -3,3 +3,5 @@ scikit-build-core>=0.10
3
3
  twine
4
4
  build
5
5
  pytest
6
+ pytest-cov
7
+ pythonnet
File without changes
@@ -0,0 +1,80 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Callable
4
+ from typing import Dict
5
+ from typing import Iterator
6
+ from typing import Optional
7
+
8
+ import pytest
9
+
10
+ from tests.sdf_factory import cleanup_sdf_dir
11
+ from tests.sdf_factory import create_engine_default_encrypted_database
12
+ from tests.sdf_factory import create_platform_default_encrypted_database
13
+ from tests.sdf_factory import create_plain_database
14
+ from tests.sdf_factory import get_sdf_dir
15
+ from tests.sdf_factory import open_connection as sdf_open_connection
16
+
17
+ BASE_DIR = Path(__file__).parent
18
+
19
+ SCENARIO_PLAIN = "plain40"
20
+ SCENARIO_PLATFORM_DEFAULT = "platform_default40"
21
+ SCENARIO_ENGINE_DEFAULT = "engine_default40"
22
+
23
+ PLATFORM_DEFAULT_PASSWORD = "P1atform!Default"
24
+ ENGINE_DEFAULT_PASSWORD = "Eng1ne-Default"
25
+
26
+
27
+ @pytest.fixture(scope="session", autouse=True)
28
+ def _clean_sdf_dir_at_session_start() -> None:
29
+ cleanup_sdf_dir(BASE_DIR)
30
+
31
+ @pytest.fixture
32
+ def sdf_dir() -> Iterator[Path]:
33
+ directory = get_sdf_dir(BASE_DIR)
34
+ yield directory
35
+ cleanup_sdf_dir(BASE_DIR)
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class SdfScenario:
40
+ name: str
41
+ path: Path
42
+ password: Optional[str]
43
+
44
+ def open_connection(self):
45
+ return sdf_open_connection(self.path, self.password)
46
+
47
+ def open_database(self):
48
+ from sqlce import SdfDatabase
49
+
50
+ if self.password is None:
51
+ return SdfDatabase(str(self.path))
52
+ return SdfDatabase(str(self.path), self.password)
53
+
54
+
55
+ def _build_plain_scenario(sdf_dir: Path) -> SdfScenario:
56
+ path = create_plain_database(sdf_dir)
57
+ return SdfScenario(name=SCENARIO_PLAIN, path=path, password=None)
58
+
59
+
60
+ def _build_platform_default_scenario(sdf_dir: Path) -> SdfScenario:
61
+ path = create_platform_default_encrypted_database(sdf_dir, PLATFORM_DEFAULT_PASSWORD)
62
+ return SdfScenario(name=SCENARIO_PLATFORM_DEFAULT, path=path, password=PLATFORM_DEFAULT_PASSWORD)
63
+
64
+
65
+ def _build_engine_default_scenario(sdf_dir: Path) -> SdfScenario:
66
+ path = create_engine_default_encrypted_database(sdf_dir, ENGINE_DEFAULT_PASSWORD)
67
+ return SdfScenario(name=SCENARIO_ENGINE_DEFAULT, path=path, password=ENGINE_DEFAULT_PASSWORD)
68
+
69
+
70
+ _SCENARIO_BUILDERS: Dict[str, Callable[[Path], SdfScenario]] = {
71
+ SCENARIO_PLAIN: _build_plain_scenario,
72
+ SCENARIO_PLATFORM_DEFAULT: _build_platform_default_scenario,
73
+ SCENARIO_ENGINE_DEFAULT: _build_engine_default_scenario,
74
+ }
75
+
76
+
77
+ @pytest.fixture(params=[SCENARIO_PLAIN, SCENARIO_PLATFORM_DEFAULT, SCENARIO_ENGINE_DEFAULT])
78
+ def sdf_scenario(request, sdf_dir: Path) -> SdfScenario:
79
+ builder = _SCENARIO_BUILDERS[request.param]
80
+ return builder(sdf_dir)