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.
- {sqlce-0.1.2 → sqlce-0.1.3}/.gitignore +1 -0
- sqlce-0.1.3/PKG-INFO +87 -0
- sqlce-0.1.3/README.md +56 -0
- sqlce-0.1.3/core/include/sdf/domain/DateTimeValue.hpp +36 -0
- sqlce-0.1.3/core/src/domain/DateTimeValue.cpp +135 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/RowDecoder.cpp +4 -4
- {sqlce-0.1.2 → sqlce-0.1.3}/docs/build.txt +2 -2
- sqlce-0.1.2/PKG-INFO → sqlce-0.1.3/docs/ru/RuReadme.md +28 -36
- {sqlce-0.1.2 → sqlce-0.1.3}/pyproject.toml +7 -1
- {sqlce-0.1.2 → sqlce-0.1.3}/requirements.txt +2 -0
- sqlce-0.1.3/tests/__init__.py +0 -0
- sqlce-0.1.3/tests/conftest.py +80 -0
- sqlce-0.1.3/tests/edge_case_tables.py +209 -0
- sqlce-0.1.3/tests/sample_tables.py +15 -0
- sqlce-0.1.3/tests/sdf_factory.py +253 -0
- sqlce-0.1.3/tests/table_spec.py +170 -0
- sqlce-0.1.3/tests/test_sdf40_edge_cases.py +120 -0
- sqlce-0.1.3/tests/test_sdf40_encrypted_engine_default.py +49 -0
- sqlce-0.1.3/tests/test_sdf40_encrypted_platform_default.py +49 -0
- sqlce-0.1.3/tests/test_sdf40_plain.py +37 -0
- sqlce-0.1.2/README.md +0 -37
- sqlce-0.1.2/core/include/sdf/domain/DateTimeValue.hpp +0 -36
- sqlce-0.1.2/core/src/domain/DateTimeValue.cpp +0 -107
- {sqlce-0.1.2 → sqlce-0.1.3}/.github/workflows/release.yml +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/CMakeLists.txt +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/LICENSE +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/bindings/CMakeLists.txt +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/bindings/src/module.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/bindings/src/value_convert.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/bindings/src/value_convert.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/CMakeLists.txt +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/application/ColumnSchema.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/application/SdfDatabase.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/ColumnDef.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/ColumnType.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/ColumnValue.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/EncryptionMode.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/Guid.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/IPageCipher.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/IPageStorage.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/NumericValue.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/PageLayout.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/Row.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/domain/TableDef.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/AesCbc.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/BinaryReader.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/FileStorage.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/PageView.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/Sha256.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/infrastructure/Sha512.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogPageScanner.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogRow.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogRowDecoder.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/CatalogSchema.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ICatalogPageScanner.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ICatalogRowDecoder.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ILobChainRegistry.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/IRowDecoder.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/ITableCatalogBuilder.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/LobChainRegistry.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/RowDecoder.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/SdfPageCipher.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/TableCatalogBuilder.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/include/sdf/parsing/TextDecoder.hpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/main.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/application/SdfDatabase.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/ColumnDef.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/ColumnType.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/ColumnValue.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/Guid.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/NumericValue.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/Row.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/domain/TableDef.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/AesCbc.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/BinaryReader.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/FileStorage.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/PageView.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/Sha256.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/infrastructure/Sha512.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogPageScanner.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogRow.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogRowDecoder.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/CatalogSchema.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/LobChainRegistry.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/SdfPageCipher.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/TableCatalogBuilder.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/src/parsing/TextDecoder.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/tests/CMakeLists.txt +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/core/tests/count_test.cpp +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/docs/CONVENTIONS.md +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/docs/en/cpp.md +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/docs/en/python.md +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/docs/ru/cpp.md +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/docs/ru/python.md +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/main.py +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/python/sqlce/__init__.py +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/python/sqlce/__init__.pyi +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/python/sqlce/_sdf_native.pyi +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/research/catalog_parse/sdf_pages.py +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/research/catalog_parse/syscatalog.py +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/research/sdf_d3.py +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/research/sdf_d4.py +0 -0
- {sqlce-0.1.2 → sqlce-0.1.3}/research/sdf_reader.py +0 -0
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
|
+

|
|
35
|
+

|
|
36
|
+

|
|
37
|
+

|
|
38
|
+

|
|
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
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
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
|
|
236
|
-
const std::
|
|
237
|
-
return domain::ColumnValue(domain::ColumnValueStorage(domain::DateTimeValue(
|
|
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.
|
|
5
|
-
git push origin v0.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
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
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/` - Биндинги
|
|
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
|
-
|
|
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
|
-
|
|
51
|
+
### Документация
|
|
65
52
|
|
|
66
|
-
|
|
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.
|
|
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"]
|
|
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)
|