totedb 0.1.0__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 (89) hide show
  1. totedb-0.1.0/CHANGELOG.md +53 -0
  2. totedb-0.1.0/LICENSE +21 -0
  3. totedb-0.1.0/MANIFEST.in +5 -0
  4. totedb-0.1.0/Makefile +309 -0
  5. totedb-0.1.0/PKG-INFO +149 -0
  6. totedb-0.1.0/README.md +127 -0
  7. totedb-0.1.0/include/queue/ack_queue.h +221 -0
  8. totedb-0.1.0/include/sql/database.h +214 -0
  9. totedb-0.1.0/include/sql/database_txn.h +124 -0
  10. totedb-0.1.0/include/sql/lexer.h +142 -0
  11. totedb-0.1.0/include/sql/mem_ledger.h +69 -0
  12. totedb-0.1.0/include/sql/result.h +239 -0
  13. totedb-0.1.0/include/sql/session.h +173 -0
  14. totedb-0.1.0/include/sql/sql_eval.h +145 -0
  15. totedb-0.1.0/include/sql/sql_statement.h +367 -0
  16. totedb-0.1.0/include/storage/catalog.h +191 -0
  17. totedb-0.1.0/include/storage/catalog_store.h +62 -0
  18. totedb-0.1.0/include/storage/checksum.h +14 -0
  19. totedb-0.1.0/include/storage/heap_store.h +301 -0
  20. totedb-0.1.0/include/storage/le.h +61 -0
  21. totedb-0.1.0/include/storage/overflow.h +65 -0
  22. totedb-0.1.0/include/storage/page_file.h +324 -0
  23. totedb-0.1.0/include/storage/pk_tree.h +98 -0
  24. totedb-0.1.0/include/storage/record.h +42 -0
  25. totedb-0.1.0/include/storage/record_page.h +43 -0
  26. totedb-0.1.0/include/storage/text_tree.h +32 -0
  27. totedb-0.1.0/include/storage/transaction.h +233 -0
  28. totedb-0.1.0/include/storage/value.h +131 -0
  29. totedb-0.1.0/include/totedb.h +179 -0
  30. totedb-0.1.0/pyproject.toml +48 -0
  31. totedb-0.1.0/python/totedb/__init__.py +78 -0
  32. totedb-0.1.0/python/totedb/_native.py +158 -0
  33. totedb-0.1.0/python/totedb/database.py +336 -0
  34. totedb-0.1.0/python/totedb/errors.py +158 -0
  35. totedb-0.1.0/python/totedb/persistqueue_compat.py +277 -0
  36. totedb-0.1.0/python/totedb/queue.py +341 -0
  37. totedb-0.1.0/python/totedb.egg-info/PKG-INFO +149 -0
  38. totedb-0.1.0/python/totedb.egg-info/SOURCES.txt +87 -0
  39. totedb-0.1.0/python/totedb.egg-info/dependency_links.txt +1 -0
  40. totedb-0.1.0/python/totedb.egg-info/top_level.txt +1 -0
  41. totedb-0.1.0/setup.cfg +4 -0
  42. totedb-0.1.0/setup.py +53 -0
  43. totedb-0.1.0/src/capi/totedb.c +707 -0
  44. totedb-0.1.0/src/queue/ack_queue.c +1111 -0
  45. totedb-0.1.0/src/queue/ack_queue_internal.h +59 -0
  46. totedb-0.1.0/src/queue/ack_queue_sql.c +146 -0
  47. totedb-0.1.0/src/queue/queue.mk +52 -0
  48. totedb-0.1.0/src/sql/database.c +1254 -0
  49. totedb-0.1.0/src/sql/database_check.c +147 -0
  50. totedb-0.1.0/src/sql/database_ddl.c +257 -0
  51. totedb-0.1.0/src/sql/database_exec.h +482 -0
  52. totedb-0.1.0/src/sql/database_expr.c +433 -0
  53. totedb-0.1.0/src/sql/database_group.c +542 -0
  54. totedb-0.1.0/src/sql/database_insert.c +568 -0
  55. totedb-0.1.0/src/sql/database_internal.h +110 -0
  56. totedb-0.1.0/src/sql/database_join.c +447 -0
  57. totedb-0.1.0/src/sql/database_select.c +626 -0
  58. totedb-0.1.0/src/sql/database_subquery.c +373 -0
  59. totedb-0.1.0/src/sql/database_txn.c +427 -0
  60. totedb-0.1.0/src/sql/database_update.c +657 -0
  61. totedb-0.1.0/src/sql/lexer.c +517 -0
  62. totedb-0.1.0/src/sql/result.c +674 -0
  63. totedb-0.1.0/src/sql/session.c +696 -0
  64. totedb-0.1.0/src/sql/sql_eval.c +1114 -0
  65. totedb-0.1.0/src/sql/sql_statement.c +1797 -0
  66. totedb-0.1.0/src/storage/catalog.c +904 -0
  67. totedb-0.1.0/src/storage/catalog_store.c +149 -0
  68. totedb-0.1.0/src/storage/checksum.c +108 -0
  69. totedb-0.1.0/src/storage/heap_store.c +2473 -0
  70. totedb-0.1.0/src/storage/overflow.c +276 -0
  71. totedb-0.1.0/src/storage/page_cache.c +192 -0
  72. totedb-0.1.0/src/storage/page_cache.h +43 -0
  73. totedb-0.1.0/src/storage/page_file.c +1692 -0
  74. totedb-0.1.0/src/storage/pk_tree.c +497 -0
  75. totedb-0.1.0/src/storage/record.c +257 -0
  76. totedb-0.1.0/src/storage/record_page.c +179 -0
  77. totedb-0.1.0/src/storage/text_tree.c +482 -0
  78. totedb-0.1.0/src/storage/transaction.c +809 -0
  79. totedb-0.1.0/src/storage/value.c +499 -0
  80. totedb-0.1.0/tests/package_smoke.py +46 -0
  81. totedb-0.1.0/tests/test_ack_queue_markchain_contract.py +167 -0
  82. totedb-0.1.0/tests/test_ack_queue_python.py +353 -0
  83. totedb-0.1.0/tests/test_database_python.py +275 -0
  84. totedb-0.1.0/tests/test_fault_fixture.py +105 -0
  85. totedb-0.1.0/tests/test_fault_oracle.py +111 -0
  86. totedb-0.1.0/tests/test_fault_runner.py +216 -0
  87. totedb-0.1.0/tests/test_multiprocess.py +249 -0
  88. totedb-0.1.0/tests/test_outbox_example.py +22 -0
  89. totedb-0.1.0/tests/test_sqlite_session_diff.py +416 -0
@@ -0,0 +1,53 @@
1
+ # 변경 기록
2
+
3
+ 버전별 주요 변경을 적는다. 형식: [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/). 버전: [유의적 버전](https://semver.org/lang/ko/). 1.0 전에는 파일 형식과 API가 호환 없이 바뀔 수 있다.
4
+
5
+ ## 0.1.0 - 2026-10-17
6
+
7
+ 첫 공개판이다.
8
+
9
+ ### 추가
10
+
11
+ - PyPI 패키지 배포와 태그 기반 릴리스 워크플로 추가 (#205)
12
+ - 여러 프로세스의 같은 파일 쓰기 적용 ([0d8ed17](https://github.com/woonyong-choi/totedb/commit/0d8ed17))
13
+ - 쓰는 동안 다른 프로세스의 조회 비차단 적용 ([6e2b94c](https://github.com/woonyong-choi/totedb/commit/6e2b94c))
14
+ - Python 생산자와 C worker의 같은 큐 공유 예제 추가 ([58c1e40](https://github.com/woonyong-choi/totedb/commit/58c1e40))
15
+ - lease 연장·idempotency 키 보존·payload 상한 옵션 추가 ([b8fc5ee](https://github.com/woonyong-choi/totedb/commit/b8fc5ee))
16
+ - 페이지 회계 검사와 큰 값 장애 시험 추가 ([83fa0dc](https://github.com/woonyong-choi/totedb/commit/83fa0dc))
17
+ - 결과 열 형식·NULL 가능 정보 전달 추가 ([63df0e2](https://github.com/woonyong-choi/totedb/commit/63df0e2))
18
+ - 정수 기본 키 범위 조회 도입 ([3dbf759](https://github.com/woonyong-choi/totedb/commit/3dbf759))
19
+ - `INSERT ... ON CONFLICT DO NOTHING` / `DO UPDATE` 지원 ([8a0c813](https://github.com/woonyong-choi/totedb/commit/8a0c813))
20
+ - 바깥 열을 참조하는 서브쿼리(상관 서브쿼리) 지원 ([27bbdf1](https://github.com/woonyong-choi/totedb/commit/27bbdf1))
21
+ - RIGHT [OUTER] JOIN 지원(첫 결합) ([92dc042](https://github.com/woonyong-choi/totedb/commit/92dc042))
22
+ - 컬럼 DEFAULT와 `INSERT ... DEFAULT VALUES` 지원, 기본 키 NOT NULL ([6a2a266](https://github.com/woonyong-choi/totedb/commit/6a2a266))
23
+ - 서브쿼리(스칼라·EXISTS·IN)와 WITH, FROM 서브쿼리 지원 ([6a17465](https://github.com/woonyong-choi/totedb/commit/6a17465))
24
+ - UNION·UNION ALL·EXCEPT·INTERSECT 지원 ([c955372](https://github.com/woonyong-choi/totedb/commit/c955372))
25
+ - INSERT·UPDATE·DELETE RETURNING 지원 ([255be69](https://github.com/woonyong-choi/totedb/commit/255be69))
26
+ - INNER·LEFT·CROSS JOIN과 여러 테이블 FROM 지원 ([e905203](https://github.com/woonyong-choi/totedb/commit/e905203))
27
+ - 집계 함수와 GROUP BY·HAVING 지원 ([49962e7](https://github.com/woonyong-choi/totedb/commit/49962e7))
28
+ - 식 트리·평가기와 SELECT 정렬·제한·중복 제거·별칭 추가 ([eb03dc5](https://github.com/woonyong-choi/totedb/commit/eb03dc5))
29
+ - 빈 페이지 목록을 영속하고 새 페이지를 즉시 재사용 ([ed52fc8](https://github.com/woonyong-choi/totedb/commit/ed52fc8))
30
+ - 일반 컬럼 B+Tree 색인과 고유 제약 추가 ([efea9f0](https://github.com/woonyong-choi/totedb/commit/efea9f0))
31
+ - ACK 큐 Python 어댑터와 공유 라이브러리 빌드 추가 ([164bef8](https://github.com/woonyong-choi/totedb/commit/164bef8))
32
+
33
+ ### 변경
34
+
35
+ - 행 인코딩을 varint 코드 형식으로 변경 ([a932fa7](https://github.com/woonyong-choi/totedb/commit/a932fa7))
36
+ - 행 하나를 꺼낼 때 그 slot의 경계만 검사하도록 개선 ([6790be5](https://github.com/woonyong-choi/totedb/commit/6790be5))
37
+ - 기본 키 순서 보조 색인과 색인 순서 실행 도입 ([e18b89f](https://github.com/woonyong-choi/totedb/commit/e18b89f))
38
+ - GROUP BY·집계를 해시 집계로 변경 ([806f5c4](https://github.com/woonyong-choi/totedb/commit/806f5c4))
39
+ - commit을 fsync 한 번으로 줄이고 끝나지 않은 commit은 열 때 되돌림 ([b0d2234](https://github.com/woonyong-choi/totedb/commit/b0d2234))
40
+ - 페이지·overflow 체크섬을 하드웨어 CRC32C로 교체 ([fce7efd](https://github.com/woonyong-choi/totedb/commit/fce7efd))
41
+ - page_file에 검증된 페이지 읽기 캐시 추가 ([d039421](https://github.com/woonyong-choi/totedb/commit/d039421))
42
+
43
+ ### 제거
44
+
45
+ - 옛 엔진과 REPL·HTTP 서버 제거 ([5e6b265](https://github.com/woonyong-choi/totedb/commit/5e6b265))
46
+ - 이전 형식 호환 코드 제거 ([c49bb6a](https://github.com/woonyong-choi/totedb/commit/c49bb6a))
47
+
48
+ ### 수정
49
+
50
+ - 큰 id를 넣은 뒤 자동 id가 오류로 끝나는 문제 수정 ([87e376f](https://github.com/woonyong-choi/totedb/commit/87e376f))
51
+ - 표 페이지 목록을 모을 때 목록 끝 밖에 쓰는 문제 수정 ([b40cc06](https://github.com/woonyong-choi/totedb/commit/b40cc06))
52
+ - 함수·CAST 결과를 SQLite·PostgreSQL 기준으로 수정 ([216e95c](https://github.com/woonyong-choi/totedb/commit/216e95c))
53
+ - 보조 색인 순회의 LIMIT 멈춤 처리 수정 ([7b7a839](https://github.com/woonyong-choi/totedb/commit/7b7a839))
totedb-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Woonyong Choi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ # sdist에서 공유 라이브러리를 다시 빌드할 때 필요한 C 소스와 빌드 규칙
2
+ include Makefile CHANGELOG.md
3
+ recursive-include include *.h
4
+ recursive-include src *.c *.h *.mk
5
+ include tests/package_smoke.py
totedb-0.1.0/Makefile ADDED
@@ -0,0 +1,309 @@
1
+ CC = gcc
2
+ WARNFLAGS = -Wall -Wextra -Werror
3
+ INCLUDES = -Iinclude
4
+ # ── sanitizer 기본값 ──
5
+ #
6
+ # macOS 26(Darwin 25) 이상에서는 ASan 런타임이 초기화 도중 자기 자신을
7
+ # 재진입해(AsanInitInternal → ... → malloc → AsanInitFromRtl) spin lock 에
8
+ # 걸린다. 프로세스가 main() 에 닿지 못하므로 ASan 을 켠 빌드는 무엇을 하든
9
+ # 멈춘다. ToteDB 의 문제가 아니라 플랫폼 문제이고, 빈 main() 으로도 재현된다.
10
+ # 그 환경에서는 UBSan 만 켠다. Linux CI 는 아래 기본값대로 둘 다 켠다.
11
+ #
12
+ # make SANITIZE=address,undefined 강제로 둘 다
13
+ # make SANITIZE= sanitizer 없이
14
+ UNAME_S := $(shell uname -s)
15
+ ifeq ($(UNAME_S),Darwin)
16
+ ifeq ($(shell test $$(uname -r | cut -d. -f1) -ge 25 && echo yes),yes)
17
+ SANITIZE ?= undefined
18
+ endif
19
+ endif
20
+ SANITIZE ?= address,undefined
21
+
22
+ ifneq ($(strip $(SANITIZE)),)
23
+ SANITIZE_FLAGS = -fsanitize=$(SANITIZE)
24
+ endif
25
+
26
+ CFLAGS = $(WARNFLAGS) -g $(INCLUDES) $(SANITIZE_FLAGS)
27
+ LDFLAGS = $(SANITIZE_FLAGS)
28
+ LDLIBS = -lpthread -lm
29
+ DEPFLAGS = -MMD -MP
30
+ PYTHON ?= python3
31
+ PYTEST ?= $(PYTHON) -m pytest
32
+ TEST_TIMEOUT ?= 60
33
+ FAULT_SEED ?= 1
34
+ TEST_RUNNER = $(PYTHON) tests/fault_runner.py --timeout $(TEST_TIMEOUT) --seed $(FAULT_SEED)
35
+ .DEFAULT_GOAL := all
36
+
37
+ SRC_DIR = src
38
+ BUILD_DIR = build
39
+
40
+ $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
41
+ @mkdir -p $(dir $@)
42
+ $(CC) $(CFLAGS) $(DEPFLAGS) -c -o $@ $<
43
+
44
+ # 각 테스트 번역 단위도 헤더 의존성을 추적한다.
45
+ $(BUILD_DIR)/tests/%.o: tests/%.c
46
+ @mkdir -p $(dir $@)
47
+ $(CC) $(CFLAGS) $(DEPFLAGS) -c -o $@ $<
48
+
49
+ STORAGE_OBJS = $(BUILD_DIR)/storage/record.o $(BUILD_DIR)/storage/record_page.o \
50
+ $(BUILD_DIR)/storage/overflow.o $(BUILD_DIR)/storage/checksum.o $(BUILD_DIR)/storage/value.o $(BUILD_DIR)/storage/catalog.o \
51
+ $(BUILD_DIR)/storage/page_file.o $(BUILD_DIR)/storage/page_cache.o $(BUILD_DIR)/storage/catalog_store.o \
52
+ $(BUILD_DIR)/storage/pk_tree.o $(BUILD_DIR)/storage/text_tree.o
53
+ BASIC_TESTS = test_record test_record_fault test_overflow test_overflow_fault test_value test_value_fault \
54
+ test_catalog test_catalog_fault test_lexer test_lexer_fault test_page_file test_catalog_store \
55
+ test_pk_tree test_text_tree
56
+ TEST_BINS = $(addprefix $(BUILD_DIR)/,$(BASIC_TESTS))
57
+
58
+ $(BUILD_DIR)/test_record: $(BUILD_DIR)/tests/test_record.o $(BUILD_DIR)/storage/record.o $(BUILD_DIR)/storage/record_page.o
59
+ $(BUILD_DIR)/test_overflow: $(BUILD_DIR)/tests/test_overflow.o $(BUILD_DIR)/storage/overflow.o $(BUILD_DIR)/storage/checksum.o $(BUILD_DIR)/storage/record_page.o
60
+ $(BUILD_DIR)/test_overflow_file: $(BUILD_DIR)/tests/test_overflow_file.o $(BUILD_DIR)/storage/overflow.o $(BUILD_DIR)/storage/checksum.o
61
+ $(BUILD_DIR)/test_value: $(BUILD_DIR)/tests/test_value.o $(BUILD_DIR)/storage/value.o
62
+ $(BUILD_DIR)/storage/value_varchar_hook.o: src/storage/value.c
63
+ @mkdir -p $(dir $@)
64
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=value_test_malloc -c -o $@ $<
65
+
66
+ $(BUILD_DIR)/test_value_varchar: $(BUILD_DIR)/tests/test_value_varchar.o $(BUILD_DIR)/storage/value_varchar_hook.o
67
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
68
+
69
+ $(BUILD_DIR)/test_value_sqlite: $(BUILD_DIR)/tests/test_value_sqlite.o $(BUILD_DIR)/storage/value.o
70
+ $(BUILD_DIR)/test_value_sqlite: LDLIBS += -lsqlite3
71
+ $(BUILD_DIR)/test_record_fault: $(BUILD_DIR)/tests/test_record_fault.o $(BUILD_DIR)/storage/record_fault.o $(BUILD_DIR)/storage/record_page_fault.o
72
+ $(BUILD_DIR)/test_overflow_fault: $(BUILD_DIR)/tests/test_overflow_fault.o $(BUILD_DIR)/storage/overflow_fault.o $(BUILD_DIR)/storage/checksum.o
73
+ $(BUILD_DIR)/test_value_fault: $(BUILD_DIR)/tests/test_value_fault.o $(BUILD_DIR)/storage/value_fault.o
74
+ $(BUILD_DIR)/test_catalog: $(BUILD_DIR)/tests/test_catalog.o $(BUILD_DIR)/storage/catalog.o
75
+ $(BUILD_DIR)/test_catalog_fault: $(BUILD_DIR)/tests/test_catalog_fault.o $(BUILD_DIR)/storage/catalog_fault.o
76
+ $(BUILD_DIR)/test_lexer: $(BUILD_DIR)/tests/test_lexer.o $(BUILD_DIR)/sql/lexer.o
77
+ $(BUILD_DIR)/test_lexer_fault: $(BUILD_DIR)/tests/test_lexer_fault.o $(BUILD_DIR)/sql/lexer_fault.o
78
+ $(BUILD_DIR)/test_page_file: $(BUILD_DIR)/tests/test_page_file.o $(STORAGE_OBJS)
79
+ $(BUILD_DIR)/test_catalog_store: $(BUILD_DIR)/tests/test_catalog_store.o $(STORAGE_OBJS)
80
+ $(BUILD_DIR)/test_pk_tree: $(BUILD_DIR)/tests/test_pk_tree.o $(BUILD_DIR)/storage/pk_tree.o
81
+ $(BUILD_DIR)/test_text_tree: $(BUILD_DIR)/tests/test_text_tree.o $(BUILD_DIR)/storage/text_tree.o
82
+
83
+ $(BUILD_DIR)/test_result: $(BUILD_DIR)/tests/test_result.o $(BUILD_DIR)/sql/result.o $(BUILD_DIR)/storage/value.o
84
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
85
+
86
+ $(BUILD_DIR)/test_result_fault: $(BUILD_DIR)/tests/test_result_fault.o $(BUILD_DIR)/sql/result_fault.o $(BUILD_DIR)/storage/value.o
87
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
88
+
89
+ $(BUILD_DIR)/sql/result_fault.o: src/sql/result.c
90
+ @mkdir -p $(dir $@)
91
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=result_test_malloc -Dcalloc=result_test_calloc -Drealloc=result_test_realloc -Dfree=result_test_free -c -o $@ $<
92
+
93
+ test-result: $(BUILD_DIR)/test_result $(BUILD_DIR)/test_result_fault
94
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_result.json -- ./$(BUILD_DIR)/test_result
95
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_result_fault.json -- ./$(BUILD_DIR)/test_result_fault
96
+
97
+ $(TEST_BINS) $(BUILD_DIR)/test_overflow_file $(BUILD_DIR)/test_value_sqlite:
98
+ $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
99
+
100
+ $(BUILD_DIR)/storage/record_fault.o: src/storage/record.c
101
+ @mkdir -p $(dir $@)
102
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=record_test_malloc -c -o $@ $<
103
+
104
+ $(BUILD_DIR)/storage/record_page_fault.o: src/storage/record_page.c
105
+ @mkdir -p $(dir $@)
106
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dcalloc=record_test_calloc -c -o $@ $<
107
+
108
+ $(BUILD_DIR)/storage/overflow_fault.o: src/storage/overflow.c
109
+ @mkdir -p $(dir $@)
110
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=overflow_test_malloc -c -o $@ $<
111
+
112
+ $(BUILD_DIR)/storage/value_fault.o: src/storage/value.c
113
+ @mkdir -p $(dir $@)
114
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=value_test_malloc -c -o $@ $<
115
+
116
+ $(BUILD_DIR)/storage/catalog_fault.o: src/storage/catalog.c
117
+ @mkdir -p $(dir $@)
118
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=catalog_test_malloc -Drealloc=catalog_test_realloc -Dfree=catalog_test_free -c -o $@ $<
119
+
120
+ $(BUILD_DIR)/sql/lexer_fault.o: src/sql/lexer.c
121
+ @mkdir -p $(dir $@)
122
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=lexer_test_malloc -Dfree=lexer_test_free -c -o $@ $<
123
+
124
+ test: $(TEST_BINS)
125
+ @set -e; for binary in $(TEST_BINS); do \
126
+ $(TEST_RUNNER) --report "$(BUILD_DIR)/fault/$${binary##*/}.json" -- "$$binary"; \
127
+ done
128
+
129
+ test-overflow-file: $(BUILD_DIR)/test_overflow_file
130
+ @directory=$$(mktemp -d "$(BUILD_DIR)/overflow.XXXXXX") && \
131
+ trap 'rmdir "$$directory" 2>/dev/null || true' EXIT HUP INT TERM && \
132
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_overflow_file.json -- ./$(BUILD_DIR)/test_overflow_file "$$directory/roundtrip.db"
133
+
134
+ test-value-sqlite: $(BUILD_DIR)/test_value_sqlite
135
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_value_sqlite.json -- ./$(BUILD_DIR)/test_value_sqlite
136
+
137
+ test-value-varchar: $(BUILD_DIR)/test_value_varchar
138
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_value_varchar.json -- ./$(BUILD_DIR)/test_value_varchar
139
+
140
+ test-runner:
141
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_runner.json -- $(PYTEST) -q tests/test_fault_runner.py tests/test_fault_oracle.py --basetemp=$(BUILD_DIR)/runner-tests -p no:cacheprovider
142
+
143
+ $(BUILD_DIR)/test_fault_runtime: $(BUILD_DIR)/tests/test_fault_runtime.o $(BUILD_DIR)/tests/fault_runtime.o
144
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
145
+
146
+ $(BUILD_DIR)/fault_fixture: $(BUILD_DIR)/tests/fault_fixture.o $(BUILD_DIR)/tests/fault_runtime.o
147
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
148
+
149
+ test-fault: $(BUILD_DIR)/test_fault_runtime $(BUILD_DIR)/fault_fixture
150
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_fault_runtime.json -- ./$(BUILD_DIR)/test_fault_runtime $(BUILD_DIR)/fault/runtime.trace
151
+ FAULT_FIXTURE=$(abspath $(BUILD_DIR)/fault_fixture) $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_fault_fixture.json -- $(PYTEST) -q tests/test_fault_fixture.py --basetemp=$(BUILD_DIR)/fault/fixture-tests -p no:cacheprovider
152
+
153
+ # SQL 실행기(#198). 파일마다 한 역할이며 공유 타입은 src/sql/database_exec.h에 있다
154
+ DATABASE_EXEC_NAMES = database database_expr database_subquery database_ddl database_insert database_select \
155
+ database_group database_join database_update database_check
156
+ DATABASE_EXEC_OBJS = $(addprefix $(BUILD_DIR)/sql/,$(addsuffix .o,$(DATABASE_EXEC_NAMES)))
157
+ DATABASE_EXEC_FAULT_OBJS = $(addprefix $(BUILD_DIR)/sql/,$(addsuffix _fault.o,$(DATABASE_EXEC_NAMES)))
158
+ DATABASE_OBJS = $(STORAGE_OBJS) $(BUILD_DIR)/storage/heap_store.o $(BUILD_DIR)/storage/transaction.o \
159
+ $(DATABASE_EXEC_OBJS) $(BUILD_DIR)/sql/database_txn.o \
160
+ $(BUILD_DIR)/sql/sql_statement.o $(BUILD_DIR)/sql/sql_eval.o $(BUILD_DIR)/sql/lexer.o $(BUILD_DIR)/sql/result.o
161
+ DATABASE_TESTS = test_sql_statement test_heap_store test_database test_database_sqlite test_database_fault test_database_bounded_open test_primary_index
162
+ DATABASE_BINS = $(addprefix $(BUILD_DIR)/,$(DATABASE_TESTS))
163
+ DATABASE_AUDIT_OBJS = $(patsubst $(BUILD_DIR)/%.o,$(BUILD_DIR)/audit/%.o,$(DATABASE_OBJS))
164
+
165
+ $(BUILD_DIR)/test_sql_statement: $(BUILD_DIR)/tests/test_sql_statement.o $(BUILD_DIR)/sql/sql_statement_fault.o $(BUILD_DIR)/sql/lexer.o $(BUILD_DIR)/storage/value_statement_fault.o
166
+ $(BUILD_DIR)/test_heap_store: $(BUILD_DIR)/tests/test_heap_store.o $(STORAGE_OBJS) $(BUILD_DIR)/storage/heap_store_test.o
167
+ $(BUILD_DIR)/test_database: $(BUILD_DIR)/tests/test_database.o $(DATABASE_OBJS)
168
+ $(BUILD_DIR)/bench_secondary_index: $(BUILD_DIR)/tests/bench_secondary_index.o $(DATABASE_OBJS)
169
+ $(BUILD_DIR)/bench_secondary_index: LDLIBS += -lsqlite3
170
+ $(BUILD_DIR)/bench_secondary_index:
171
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
172
+ $(BUILD_DIR)/bench_primary_range: $(BUILD_DIR)/tests/bench_primary_range.o $(DATABASE_OBJS)
173
+ $(BUILD_DIR)/bench_primary_range: LDLIBS += -lsqlite3
174
+ $(BUILD_DIR)/bench_primary_range:
175
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
176
+ $(BUILD_DIR)/bench_free_space: $(BUILD_DIR)/tests/bench_free_space.o $(DATABASE_OBJS)
177
+ $(BUILD_DIR)/bench_free_space: LDLIBS += -lsqlite3
178
+ $(BUILD_DIR)/bench_free_space:
179
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
180
+ $(BUILD_DIR)/test_database_sqlite: $(BUILD_DIR)/tests/test_database_sqlite.o $(DATABASE_OBJS)
181
+ $(BUILD_DIR)/test_primary_index: $(BUILD_DIR)/tests/test_primary_index.o $(DATABASE_OBJS)
182
+ $(BUILD_DIR)/test_database_fault: $(BUILD_DIR)/tests/test_database_fault.o $(filter-out $(DATABASE_EXEC_OBJS) $(BUILD_DIR)/storage/heap_store.o,$(DATABASE_OBJS)) $(DATABASE_EXEC_FAULT_OBJS) $(BUILD_DIR)/storage/heap_store_fault.o
183
+ $(BUILD_DIR)/test_database_bounded_open: $(BUILD_DIR)/tests/test_database_bounded_open.o $(BUILD_DIR)/tests/alloc_audit.o $(DATABASE_AUDIT_OBJS)
184
+ $(DATABASE_BINS):
185
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
186
+
187
+ $(BUILD_DIR)/audit/%.o: src/%.c
188
+ @mkdir -p $(dir $@)
189
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=audit_malloc -Dcalloc=audit_calloc -Drealloc=audit_realloc -Dfree=audit_free -c -o $@ $<
190
+
191
+ $(BUILD_DIR)/sql/sql_statement_fault.o: src/sql/sql_statement.c
192
+ @mkdir -p $(dir $@)
193
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=sqlstmt_test_malloc -Dcalloc=sqlstmt_test_calloc -Dfree=sqlstmt_test_free -c -o $@ $<
194
+
195
+ $(BUILD_DIR)/storage/value_statement_fault.o: src/storage/value.c
196
+ @mkdir -p $(dir $@)
197
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=sqlstmt_test_malloc -Dcalloc=sqlstmt_test_calloc -Dfree=sqlstmt_test_free -c -o $@ $<
198
+
199
+ $(BUILD_DIR)/storage/heap_store_test.o: src/storage/heap_store.c
200
+ @mkdir -p $(dir $@)
201
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=hs_test_malloc -Dcalloc=hs_test_calloc -Drealloc=hs_test_realloc -c -o $@ $<
202
+
203
+ $(DATABASE_EXEC_FAULT_OBJS): $(BUILD_DIR)/sql/%_fault.o: src/sql/%.c
204
+ @mkdir -p $(dir $@)
205
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=db_test_malloc -Dcalloc=db_test_calloc -Drealloc=db_test_realloc -Dfree=db_test_free -c -o $@ $<
206
+
207
+ $(BUILD_DIR)/storage/heap_store_fault.o: src/storage/heap_store.c
208
+ @mkdir -p $(dir $@)
209
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=db_test_malloc -Dcalloc=db_test_calloc -Drealloc=db_test_realloc -Dfree=db_test_free -c -o $@ $<
210
+
211
+ test-database: $(DATABASE_BINS)
212
+ @set -e; for binary in $(DATABASE_BINS); do \
213
+ $(TEST_RUNNER) --report "$(BUILD_DIR)/fault/$${binary##*/}.json" -- "$$binary"; \
214
+ done
215
+
216
+ $(BUILD_DIR)/sql/session_test.o: src/sql/session.c
217
+ @mkdir -p $(dir $@)
218
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dcalloc=st_calloc -Dfree=st_free -c -o $@ $<
219
+
220
+ $(BUILD_DIR)/audit/sql/session_test.o: src/sql/session.c
221
+ @mkdir -p $(dir $@)
222
+ $(CC) $(CFLAGS) $(DEPFLAGS) -Dmalloc=audit_malloc -Dcalloc=audit_calloc -Drealloc=audit_realloc -Dfree=audit_free -DSESSION_TEST_HOOK -c -o $@ $<
223
+
224
+ $(BUILD_DIR)/test_session: $(BUILD_DIR)/tests/test_session.o $(BUILD_DIR)/sql/session_test.o $(DATABASE_OBJS)
225
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
226
+
227
+ $(BUILD_DIR)/test_session_audit: $(BUILD_DIR)/tests/test_session_audit.o $(BUILD_DIR)/tests/alloc_audit.o $(BUILD_DIR)/audit/sql/session_test.o $(DATABASE_AUDIT_OBJS)
228
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
229
+
230
+ $(BUILD_DIR)/test_result_ledger: $(BUILD_DIR)/tests/test_result_ledger.o $(BUILD_DIR)/tests/alloc_audit.o $(BUILD_DIR)/audit/sql/result.o $(BUILD_DIR)/storage/value.o
231
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
232
+
233
+ $(BUILD_DIR)/test_heap_ledger: $(BUILD_DIR)/tests/test_heap_ledger.o $(BUILD_DIR)/tests/alloc_audit.o $(BUILD_DIR)/audit/storage/heap_store.o $(BUILD_DIR)/audit/storage/record.o $(filter-out $(BUILD_DIR)/storage/record.o,$(STORAGE_OBJS))
234
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
235
+
236
+ test-session: $(BUILD_DIR)/test_session $(BUILD_DIR)/test_session_audit $(BUILD_DIR)/test_result_ledger $(BUILD_DIR)/test_heap_ledger
237
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_session.json -- ./$(BUILD_DIR)/test_session
238
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_session_audit.json -- ./$(BUILD_DIR)/test_session_audit
239
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_result_ledger.json -- ./$(BUILD_DIR)/test_result_ledger
240
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_heap_ledger.json -- ./$(BUILD_DIR)/test_heap_ledger
241
+
242
+ $(BUILD_DIR)/test_transaction: $(BUILD_DIR)/tests/test_transaction.o $(BUILD_DIR)/storage/transaction.o
243
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
244
+
245
+ $(BUILD_DIR)/test_transaction_fault: $(BUILD_DIR)/tests/test_transaction_fault.o
246
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
247
+
248
+ $(BUILD_DIR)/test_database_txn: $(BUILD_DIR)/tests/test_database_txn.o $(DATABASE_OBJS)
249
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
250
+
251
+ $(BUILD_DIR)/test_session_txn: $(BUILD_DIR)/tests/test_session_txn.o $(BUILD_DIR)/sql/session.o $(DATABASE_OBJS)
252
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
253
+
254
+ test-transaction: $(BUILD_DIR)/test_transaction $(BUILD_DIR)/test_transaction_fault $(BUILD_DIR)/test_database_txn $(BUILD_DIR)/test_session_txn
255
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_transaction.json -- ./$(BUILD_DIR)/test_transaction
256
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_transaction_fault.json -- ./$(BUILD_DIR)/test_transaction_fault
257
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_database_txn.json -- ./$(BUILD_DIR)/test_database_txn
258
+ $(TEST_RUNNER) --report $(BUILD_DIR)/fault/test_session_txn.json -- ./$(BUILD_DIR)/test_session_txn
259
+
260
+ $(BUILD_DIR)/session_probe: $(BUILD_DIR)/tests/session_probe.o $(BUILD_DIR)/sql/session.o $(DATABASE_OBJS)
261
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
262
+
263
+ # SQLite 3.51.0과 같은 입력으로 두 연결을 비교한다. 시스템 SQLite 버전과 상관없이 기준 버전 소스를 받아
264
+ # (sha256 확인) 공유 라이브러리로 빌드하고 파이썬 sqlite3 모듈이 그것을 쓰게 한다. 네트워크가 필요해 test-all에 넣지 않는다
265
+ SQLITE_DIFF_DIR ?= $(BUILD_DIR)/sqlite-session-diff
266
+ SQLITE_REF_VERSION := 3510000
267
+ SQLITE_REF_URL := https://www.sqlite.org/2025/sqlite-amalgamation-$(SQLITE_REF_VERSION).zip
268
+ SQLITE_REF_SHA256 := 1caf7116f2910600d04473ad69d37ec538fa62fa36adccd37b5e0e43647c98be
269
+ SQLITE_REF_DIR := $(BUILD_DIR)/sqlite-ref
270
+
271
+ $(SQLITE_REF_DIR)/libsqlite3.so.0:
272
+ mkdir -p $(SQLITE_REF_DIR)
273
+ curl -sSfL -o $(SQLITE_REF_DIR)/src.zip $(SQLITE_REF_URL)
274
+ echo "$(SQLITE_REF_SHA256) $(SQLITE_REF_DIR)/src.zip" | sha256sum -c --quiet -
275
+ unzip -qo -d $(SQLITE_REF_DIR) $(SQLITE_REF_DIR)/src.zip
276
+ gcc -O2 -fPIC -shared -Wl,-soname,libsqlite3.so.0 -o $@ \
277
+ $(SQLITE_REF_DIR)/sqlite-amalgamation-$(SQLITE_REF_VERSION)/sqlite3.c -lm -lpthread
278
+
279
+ test-sqlite-session-diff-harness:
280
+ $(PYTEST) -q tests/test_sqlite_session_diff.py --basetemp=$(BUILD_DIR)/sqlite-session-diff-harness -p no:cacheprovider
281
+
282
+ test-sqlite-session-diff: $(BUILD_DIR)/session_probe test-sqlite-session-diff-harness $(SQLITE_REF_DIR)/libsqlite3.so.0
283
+ LD_LIBRARY_PATH=$(abspath $(SQLITE_REF_DIR)) $(PYTHON) tests/sqlite_session_diff.py \
284
+ --probe ./$(BUILD_DIR)/session_probe --workdir $(SQLITE_DIFF_DIR)
285
+
286
+ test-all: test test-overflow-file test-value-sqlite test-value-varchar test-database test-transaction test-session test-ack-queue test-ack-queue-python
287
+
288
+ clean:
289
+ rm -rf $(BUILD_DIR)
290
+
291
+ .PHONY: test-runner test-fault test-result test-overflow-file test-value-sqlite test-value-varchar test-sqlite-session-diff test-sqlite-session-diff-harness
292
+
293
+ -include $(STORAGE_OBJS:.o=.d)
294
+ -include $(wildcard $(BUILD_DIR)/tests/*.d $(BUILD_DIR)/storage/*_fault.d $(BUILD_DIR)/sql/lexer*.d)
295
+ -include $(BUILD_DIR)/sql/result.d $(BUILD_DIR)/sql/result_fault.d
296
+ -include $(DATABASE_OBJS:.o=.d) $(DATABASE_EXEC_FAULT_OBJS:.o=.d) $(BUILD_DIR)/sql/sql_statement_fault.d $(BUILD_DIR)/storage/heap_store_test.d
297
+ -include $(DATABASE_AUDIT_OBJS:.o=.d)
298
+
299
+ -include $(BUILD_DIR)/storage/transaction.d
300
+
301
+ .PHONY: test-database test-transaction test-session
302
+
303
+ .PHONY: all test test-all clean
304
+
305
+ # 선택형 ACK 큐(#140). 큐 파일이 없으면 무시한다.
306
+ -include src/queue/queue.mk
307
+
308
+ # 기본 대상: DB와 큐를 묶은 공유 라이브러리(파이썬 바인딩이 쓴다)
309
+ all: $(TOTEDB_SHARED_LIB)
totedb-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: totedb
3
+ Version: 0.1.0
4
+ Summary: Embedded SQL database with a job queue that commits in the same transaction (Transactional Outbox Table Engine)
5
+ Author: woonyong-choi
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/woonyong-choi/totedb
8
+ Project-URL: Changelog, https://github.com/woonyong-choi/totedb/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/woonyong-choi/totedb/issues
10
+ Keywords: database,embedded,sql,queue,outbox,transaction
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Programming Language :: C
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Database :: Database Engines/Servers
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: license-file
22
+
23
+ <p align="center">
24
+ <img src="docs/assets/totedb-icon.png" alt="ToteDB 아이콘: 짙은 남색 토트백" width="160">
25
+ </p>
26
+
27
+ <h1 align="center">ToteDB</h1>
28
+
29
+ <p align="center">
30
+ Transactional Outbox Table Engine<br>
31
+ 앱 안에 넣는 SQL 데이터베이스와, 같은 트랜잭션에 들어가는 작업 큐<br>
32
+ <a href="https://github.com/woonyong-choi/totedb/actions/workflows/ci.yml"><img src="https://github.com/woonyong-choi/totedb/actions/workflows/ci.yml/badge.svg" alt="CI"></a><br>
33
+ <a href="#빠른-시작">빠른 시작</a> · <a href="#작동-방식">작동 방식</a> · <a href="#상태">상태</a>
34
+ </p>
35
+
36
+ > [!NOTE]
37
+ > 첫 공개판 0.1.0입니다. 1.0 전에는 파일 형식과 API가 바뀔 수 있습니다.
38
+
39
+ ## 빠른 시작
40
+
41
+ Python 3.9 이상에서 설치하세요. Linux(x86_64·aarch64)와 macOS(arm64)는 빌드된 wheel을 받고, 그 밖의 POSIX 환경은 GCC와 GNU Make로 소스를 빌드해 설치합니다.
42
+
43
+ ```sh
44
+ pip install totedb
45
+ ```
46
+
47
+ 아래 내용을 `demo.py`로 저장하세요. 테이블을 만들고, 행 갱신과 큐 작업 추가를 한 트랜잭션으로 확정한 뒤 작업을 꺼내 처리합니다.
48
+
49
+ ```python
50
+ from totedb import Database, Queue
51
+
52
+ with Database("demo.db") as db:
53
+ db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INTEGER)")
54
+ db.execute("INSERT INTO users VALUES (?, ?, ?), (?, ?, ?)", [1, "Alice", 25, 2, "Bob", 31])
55
+ print(db.execute("SELECT name, age FROM users WHERE age > ? ORDER BY age", [20]).rows)
56
+
57
+ queue = Queue(db, "jobs")
58
+ with db.begin() as txn:
59
+ txn.execute("UPDATE users SET age = age + 1 WHERE name = ?", ["Alice"])
60
+ queue.put(b"send-welcome-mail", txn=txn)
61
+ claim = queue.get(lease_ms=30_000)
62
+ print(claim.payload)
63
+ queue.ack(claim)
64
+ print(queue.stats())
65
+ ```
66
+
67
+ ```sh
68
+ python3 demo.py
69
+ ```
70
+
71
+ ```text
72
+ (('Alice', 25), ('Bob', 31))
73
+ b'send-welcome-mail'
74
+ QueueStats(ready=0, leased=0, acked=1, failed=0)
75
+ ```
76
+
77
+ ### 여러 프로세스·언어에서 같은 큐 쓰기
78
+
79
+ 큐의 lease, fence, 멱등 키 규칙은 C 코어에 있어 어느 언어로 붙어도 같게 동작합니다. 아래 예제는 Python 프로세스가 주문 저장과 출고 작업 등록을 한 트랜잭션으로 확정하고, C worker 프로세스 셋이 같은 파일의 같은 큐를 나눠 처리합니다. 작업 하나를 claim한 worker를 강제 종료해도 lease가 끝나면 다른 worker가 그 작업을 받아 처리합니다. 코드는 [`examples/outbox/`](examples/outbox/)에 있습니다.
80
+
81
+ ```sh
82
+ make example-outbox
83
+ ```
84
+
85
+ ```text
86
+ [python] 주문 30건과 출고 작업 30건을 같은 트랜잭션으로 넣음
87
+ [c-hung] 주문 1을 claim한 뒤 SIGKILL로 강제 종료됨
88
+ [c-1] 10건 처리
89
+ [c-2] 10건 처리
90
+ [c-3] 10건 처리
91
+ [검증] 출고 30건, 중복 0, 누락 0, 큐 acked 30, 강제 종료된 worker의 주문 1은 worker c-2에 재전달되어 처리됨
92
+ 검증 통과
93
+ ```
94
+
95
+ 어느 worker가 몇 건을 처리하는지는 실행마다 다릅니다.
96
+
97
+ ## 해결하는 문제
98
+
99
+ 로컬 프로그램이 업무 데이터와 작업 큐를 따로 저장하면 한쪽만 commit되어 작업이 사라지거나 두 번 실행될 수 있습니다. ToteDB은 큐를 같은 DB 파일의 테이블로 두어 업무 갱신과 큐 변경을 한 트랜잭션으로 확정합니다. 큐를 설치하지 않으면 큐 테이블이나 객체가 생기지 않고 SQL DB로만 동작합니다. 한 부모 프로세스가 띄운 여러 worker 프로세스가 같은 파일과 같은 큐를 함께 씁니다. 쓰기는 파일 전체에서 한 번에 하나씩 확정합니다. 여러 writer가 동시에 commit해야 하거나 넓은 SQL 호환이 필요하면 SQLite나 PostgreSQL이 더 맞습니다.
100
+
101
+ ## 작동 방식
102
+
103
+ 1. 응용 프로그램이 C API `database_exec`나 Python `Database.execute`로 SQL과 매개변수를 넘깁니다.
104
+ 2. lexer와 parser가 문장 하나를 AST로 바꾸고, 실행기가 기본 키·색인 조회나 테이블 스캔으로 행을 읽습니다.
105
+ 3. 변경 문장은 바뀐 페이지를 새 자리에 쓰고, 제어 페이지 한 장으로 새 세대를 게시한 뒤 `fsync`합니다. 제어 페이지에 적은 페이지 체크섬 목록으로 끝나지 않은 commit을 알아보고 이전 세대로 엽니다.
106
+ 4. 쓰기 트랜잭션은 같은 파일을 연 모든 프로세스에서 한 번에 하나이고, 트랜잭션은 시작 때 게시된 상태와 자기 변경만 봅니다. 다른 프로세스가 commit하면 다음 문장이 새 상태를 다시 읽습니다. 다른 프로세스가 쓰는 동안에도 조회는 기다리지 않고 마지막으로 게시된 상태를 읽습니다. 다른 writer가 있으면 Python 바인딩은 `busy_timeout_s`까지 다시 시도합니다.
107
+ 5. ACK 큐는 공개 SQL과 트랜잭션 API만 써서 작업을 READY, LEASED, ACKED, FAILED 상태로 옮기고 fence 값으로 오래된 처리 결과를 거절합니다.
108
+
109
+ 전체 구조는 [아키텍처](docs/architecture.md)에 있습니다.
110
+
111
+ ## 상태
112
+
113
+ | 있는 것 | 아직 없는 것 | 다음 |
114
+ |---|---|---|
115
+ | CREATE TABLE·INDEX, INSERT·UPDATE·DELETE, DEFAULT, ON CONFLICT, RETURNING | ALTER TABLE, `INSERT ... SELECT`, 복합 색인, 창 함수, WITH RECURSIVE | — |
116
+ | SELECT의 WHERE·ORDER BY·LIMIT, JOIN(INNER·LEFT·첫 RIGHT), GROUP BY·HAVING, 상관 서브쿼리, UNION·EXCEPT·INTERSECT, WITH, 정수 기본 키 범위 조회 | FULL JOIN, USING, 보조 색인 범위 조회, 보조 색인 순서 정렬 | — |
117
+ | 여러 프로세스가 같은 파일 공유(쓰기는 한 번에 하나, 쓰는 동안에도 조회는 기다리지 않음), snapshot 격리, COW 게시와 `fsync` 뒤 commit 응답, fork한 자식의 부모 핸들 사용 거절 | 여러 writer 동시 commit, 네트워크 파일 시스템, 저장 장치가 버린 쓰기 탐지 | — |
118
+ | at-least-once ACK 큐, lease와 fence, lease 연장, 업무 갱신과 같은 commit, purge 뒤에도 보존 기간(기본 7일) 동안 유지되는 idempotency 키, 큐별 payload 상한(기본 1 MiB), 여러 프로세스·언어(Python, C)가 같은 큐 사용 | Python·C 밖의 언어 바인딩 | — |
119
+ | 시험마다 SQLite와 PostgreSQL에 같은 SQL을 실행해 결과 대조, commit 중 모든 쓰기 지점의 종료·I/O 실패 시험([안전성 시험 결과](docs/safety.md)), SQLite와 같은 입력의 CRUD·혼합 부하·강제 종료 뒤 복구 측정 | 전원 차단 검증, SQLite 수준의 파일 크기(같은 1만 행에서 약 1.6배) | — |
120
+
121
+ 1.0 전에는 파일 형식과 API가 예고 없이 바뀔 수 있습니다. 바뀐 내용은 [변경 기록](CHANGELOG.md)에 있습니다.
122
+
123
+ ## 문서
124
+
125
+ - [아키텍처](docs/architecture.md): 구성 요소, 처리 흐름, 지켜야 할 규칙
126
+ - [빌드와 시험](docs/build-and-test.md): 빌드 변수, 시험 대상, 다른 엔진과 비교, 패키지 배포
127
+ - [안전성 시험 결과](docs/safety.md): 강제 종료·I/O 실패·여러 프로세스 시험과 결과, 확인하지 않은 범위
128
+ - [C 코딩 규칙](docs/convention/c-style.md): C 코드 작성 기준
129
+ - [Python 코딩 규칙](docs/convention/python-style.md): Python 코드 작성 기준
130
+ - [폴더 구조 규칙](docs/convention/project-structure.md): 파일과 폴더를 두는 자리
131
+ - [기여 안내](.github/CONTRIBUTING.md): 검사, 커밋·PR 형식
132
+ - [전체 문서](docs/README.md)
133
+
134
+ ## 개발
135
+
136
+ 저장소에서 직접 쓰려면 공유 라이브러리를 빌드하고 `PYTHONPATH=python`을 지정하세요.
137
+
138
+ ```sh
139
+ git clone https://github.com/woonyong-choi/totedb.git
140
+ cd totedb
141
+ make all
142
+ make test-all test-runner test-fault test-result
143
+ make SANITIZE=thread BUILD_DIR=build-tsan test-transaction test-session test-ack-queue
144
+ make test-sql-diff
145
+ ```
146
+
147
+ ## 라이선스
148
+
149
+ [MIT](LICENSE)