forked from repo-mirrors/dbt-core
Compare commits
522 Commits
adding-sem
...
codecov_ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
301f0eb454 | ||
|
|
a1b067c683 | ||
|
|
cbfc6a8baf | ||
|
|
9765596247 | ||
|
|
1b1a291fae | ||
|
|
867534c1f4 | ||
|
|
6d8b6459eb | ||
|
|
203bd8defd | ||
|
|
949680a5ce | ||
|
|
015c490b63 | ||
|
|
95a916936e | ||
|
|
961d69d8c2 | ||
|
|
be4d0a5b88 | ||
|
|
5310d3715c | ||
|
|
6bdf983e0b | ||
|
|
6604b9ca31 | ||
|
|
305241fe86 | ||
|
|
2d686b73fd | ||
|
|
30def98ed9 | ||
|
|
b78d23f68d | ||
|
|
4ffd633e40 | ||
|
|
07c3dcd21c | ||
|
|
fd233eac62 | ||
|
|
d8f38ca48b | ||
|
|
7740bd6b45 | ||
|
|
a57fdf008e | ||
|
|
a8e3afe8af | ||
|
|
44572e72f0 | ||
|
|
54b1e5699c | ||
|
|
ee7bc24903 | ||
|
|
15ef88d2ed | ||
|
|
7c56d72b46 | ||
|
|
5d28e4744e | ||
|
|
746ca7d149 | ||
|
|
a58b5ee8fb | ||
|
|
7fbfd53c3e | ||
|
|
4c44c29ee4 | ||
|
|
8ee0fe0a64 | ||
|
|
307a618ea8 | ||
|
|
ce07ce58e1 | ||
|
|
7ea51df6ae | ||
|
|
fe463c79fe | ||
|
|
d7d6843c5f | ||
|
|
adcf8bcbb3 | ||
|
|
5d937802f1 | ||
|
|
8c201e88a7 | ||
|
|
b8bc264731 | ||
|
|
9c6fbff0c3 | ||
|
|
5c7aa7f9ce | ||
|
|
1af94dedad | ||
|
|
2e7c968419 | ||
|
|
05b0820a9e | ||
|
|
d4e620eb50 | ||
|
|
0f52505dbe | ||
|
|
cb754fd97b | ||
|
|
e01d4c0a6e | ||
|
|
7a6bedaae3 | ||
|
|
22145e7e5f | ||
|
|
b3ac41ff9a | ||
|
|
036b95e5b2 | ||
|
|
2ce0c5ccf5 | ||
|
|
7156cc5c1d | ||
|
|
fcd30b1de2 | ||
|
|
a84fa50166 | ||
|
|
6a1e3a6db8 | ||
|
|
b37e5b5198 | ||
|
|
f9d4e9e03d | ||
|
|
9c97d30702 | ||
|
|
9836f7bdef | ||
|
|
b07ff7aebd | ||
|
|
aecbb4564c | ||
|
|
779663b39c | ||
|
|
7934af2974 | ||
|
|
533988233e | ||
|
|
8bc0e77a1d | ||
|
|
1c93c9bb58 | ||
|
|
6d7b32977c | ||
|
|
bf15466bec | ||
|
|
fb1ebe48f0 | ||
|
|
de65697ff9 | ||
|
|
ecf90d689e | ||
|
|
4cdeff11cd | ||
|
|
9ff2f6e430 | ||
|
|
73a0dc6d14 | ||
|
|
0a1c73e0fd | ||
|
|
8653ffc5a4 | ||
|
|
86583a350f | ||
|
|
fafab5d557 | ||
|
|
39e0c22353 | ||
|
|
f767943fb2 | ||
|
|
ae97831ebf | ||
|
|
f16bae0ab9 | ||
|
|
b947b2bc7e | ||
|
|
7068688181 | ||
|
|
38c0600982 | ||
|
|
83d163add5 | ||
|
|
d46e8855ef | ||
|
|
60524c0f8e | ||
|
|
ca73a2aa15 | ||
|
|
4a833a4272 | ||
|
|
f9abeca231 | ||
|
|
5f9e527768 | ||
|
|
6f51de4cb5 | ||
|
|
cb64682d33 | ||
|
|
98d1a94b60 | ||
|
|
a89da7ca88 | ||
|
|
2d237828ae | ||
|
|
f4253da72a | ||
|
|
919822e583 | ||
|
|
444c787729 | ||
|
|
3b63dd9f11 | ||
|
|
84166bf457 | ||
|
|
dd445e1fde | ||
|
|
6a22ec1b2e | ||
|
|
587bbcbf0d | ||
|
|
8e1c4ec116 | ||
|
|
dc35f56baa | ||
|
|
60d116b5b5 | ||
|
|
4dbc4a41c4 | ||
|
|
89541faec9 | ||
|
|
79bd98560b | ||
|
|
7917bd5033 | ||
|
|
05b0ebb184 | ||
|
|
e1d7a53325 | ||
|
|
7a06d354aa | ||
|
|
9dd5ab90bf | ||
|
|
45d614533f | ||
|
|
00a531d9d6 | ||
|
|
fd301a38db | ||
|
|
9c7e01dbca | ||
|
|
1ac6df0996 | ||
|
|
38ca4fce25 | ||
|
|
7e3a6eec96 | ||
|
|
ac16a55c64 | ||
|
|
620ca40b85 | ||
|
|
aa11cf2956 | ||
|
|
feb06e2107 | ||
|
|
a3d40e0abf | ||
|
|
7c1bd91d0a | ||
|
|
70a132d059 | ||
|
|
1fdebc660b | ||
|
|
0516192d69 | ||
|
|
f99be58217 | ||
|
|
3b6222e516 | ||
|
|
b88e60f8dd | ||
|
|
9373c4d1e4 | ||
|
|
0fe3ee8eca | ||
|
|
0d71a32aa2 | ||
|
|
0f223663bb | ||
|
|
c25d0c9f9c | ||
|
|
4a4b7beeb9 | ||
|
|
265e09dc93 | ||
|
|
87ea28fe84 | ||
|
|
af0f786f2e | ||
|
|
50528a009d | ||
|
|
f6e5582370 | ||
|
|
dea3181d96 | ||
|
|
5f7ae2fd4c | ||
|
|
4f249b8652 | ||
|
|
df23f68dd4 | ||
|
|
4b091cee9e | ||
|
|
dcb5acdf29 | ||
|
|
7fbeced315 | ||
|
|
47e7b1cc80 | ||
|
|
8f998c218e | ||
|
|
41c0797d7a | ||
|
|
3f2cba0dec | ||
|
|
b60c67d107 | ||
|
|
630cd3aba0 | ||
|
|
05595f5920 | ||
|
|
29f2cfc48d | ||
|
|
43d949c5cc | ||
|
|
58312f1816 | ||
|
|
dffbb6a659 | ||
|
|
272beb21a9 | ||
|
|
d34c511fa5 | ||
|
|
2945619eb8 | ||
|
|
078a83679a | ||
|
|
881437e890 | ||
|
|
40aca4bc17 | ||
|
|
0de046dfbe | ||
|
|
5a7b73be26 | ||
|
|
35f8ceb7f1 | ||
|
|
19d6dab973 | ||
|
|
810ef7f556 | ||
|
|
fd7306643f | ||
|
|
f1dddaa6e9 | ||
|
|
a7eb89d645 | ||
|
|
c56a9b2b7f | ||
|
|
17a8f462dd | ||
|
|
e3498bdaa5 | ||
|
|
d2f963e20e | ||
|
|
d53bb37186 | ||
|
|
9874f9e004 | ||
|
|
2739d5f4c4 | ||
|
|
d07603b288 | ||
|
|
723ac9493d | ||
|
|
de75777ede | ||
|
|
75703c10ee | ||
|
|
1722079a43 | ||
|
|
f5aea191d1 | ||
|
|
b2418b0634 | ||
|
|
aac034d9ba | ||
|
|
ada8860e48 | ||
|
|
a87275a4ca | ||
|
|
0891aef8d7 | ||
|
|
add924221a | ||
|
|
ba40d07ea3 | ||
|
|
57e9096816 | ||
|
|
6fedfe0ece | ||
|
|
121fa5793f | ||
|
|
a88f640395 | ||
|
|
74419b0e86 | ||
|
|
2ddf296a8e | ||
|
|
6b42a712a8 | ||
|
|
c3230d3374 | ||
|
|
602535fe71 | ||
|
|
f9b28bcaed | ||
|
|
922c75344b | ||
|
|
2caf87c247 | ||
|
|
f2a3535c3f | ||
|
|
a500e60b7f | ||
|
|
c7ebc8935f | ||
|
|
56f8f8a329 | ||
|
|
828d723512 | ||
|
|
b450a5754e | ||
|
|
2971b9a027 | ||
|
|
3c54959829 | ||
|
|
87e25e8692 | ||
|
|
6ac5c90a0b | ||
|
|
a58fb24e2b | ||
|
|
9ce593c47f | ||
|
|
c9d4051136 | ||
|
|
26f3518cea | ||
|
|
49eed67ab0 | ||
|
|
7a4d3bd2dc | ||
|
|
2afb4ccd68 | ||
|
|
f38d5ad8e2 | ||
|
|
7e1f04c667 | ||
|
|
ef2ba39dcf | ||
|
|
7045e11aa0 | ||
|
|
a9016c37f5 | ||
|
|
fe62ab8ec5 | ||
|
|
893daedc42 | ||
|
|
44be13b006 | ||
|
|
a5131ecc7d | ||
|
|
ce5d02569f | ||
|
|
4fc7456000 | ||
|
|
28e3412556 | ||
|
|
86fe510bcf | ||
|
|
eaedbd3187 | ||
|
|
b31fcc4edf | ||
|
|
edb5634b9a | ||
|
|
ad21458e10 | ||
|
|
622bc43ced | ||
|
|
e5d99da0bc | ||
|
|
618499b379 | ||
|
|
bca361acf9 | ||
|
|
567e2ca2be | ||
|
|
474143466f | ||
|
|
050161c78f | ||
|
|
ab496af1f0 | ||
|
|
c3c2b27e97 | ||
|
|
5789d717ba | ||
|
|
14e2c3ec21 | ||
|
|
b718c537a7 | ||
|
|
6992151081 | ||
|
|
bf5ed39db3 | ||
|
|
f573870232 | ||
|
|
da4a90aa11 | ||
|
|
2cfc386773 | ||
|
|
ae485f996a | ||
|
|
73ff497200 | ||
|
|
9a7305d43f | ||
|
|
ca23148908 | ||
|
|
8225a009b5 | ||
|
|
9605b76178 | ||
|
|
137dd9aa1b | ||
|
|
a203fe866a | ||
|
|
4186f99b74 | ||
|
|
6db899eddd | ||
|
|
8ea20b4ba2 | ||
|
|
3f76f82c88 | ||
|
|
6cbf66db58 | ||
|
|
8cd11b380f | ||
|
|
814eb65d59 | ||
|
|
f24452a3ab | ||
|
|
30503697f2 | ||
|
|
90902689c3 | ||
|
|
5a0e776cff | ||
|
|
9368e7a6a1 | ||
|
|
c02ddf8c0e | ||
|
|
64b8a12a42 | ||
|
|
e895fe9e4b | ||
|
|
8d987521dd | ||
|
|
4aafc5ef4a | ||
|
|
24ca76ea58 | ||
|
|
b681908ee2 | ||
|
|
72076b3fe5 | ||
|
|
0683c59dcd | ||
|
|
8019498f09 | ||
|
|
6234aec7d2 | ||
|
|
edd8059eb3 | ||
|
|
e3be347768 | ||
|
|
597acf1fa1 | ||
|
|
effa1a0813 | ||
|
|
726800be57 | ||
|
|
8b79747908 | ||
|
|
ec5d31de0e | ||
|
|
5d61ebbfdb | ||
|
|
0ef9931d19 | ||
|
|
a2213abbc0 | ||
|
|
915585c36e | ||
|
|
5ddd40885e | ||
|
|
58d1bccd26 | ||
|
|
70c26f5c74 | ||
|
|
ac962a4a31 | ||
|
|
bb2d062cc5 | ||
|
|
7667784985 | ||
|
|
05ecfbcc3a | ||
|
|
e06ae97068 | ||
|
|
ed50877c4f | ||
|
|
6b5e38ee28 | ||
|
|
63a1bf9adb | ||
|
|
2c7238fbb4 | ||
|
|
b1d597109f | ||
|
|
7617eece3a | ||
|
|
8ce92b56d7 | ||
|
|
21fae1c4a4 | ||
|
|
c952d44ec5 | ||
|
|
971b38c26b | ||
|
|
b7884facbf | ||
|
|
57ce461067 | ||
|
|
b1b830643e | ||
|
|
3cee9d16fa | ||
|
|
c647706ac2 | ||
|
|
7b33ffb1bd | ||
|
|
f38cbc4feb | ||
|
|
480e0e55c5 | ||
|
|
e5c468bb93 | ||
|
|
605c72e86e | ||
|
|
aad46ac5a8 | ||
|
|
d85618ef26 | ||
|
|
1250f23c44 | ||
|
|
daea7d59a7 | ||
|
|
4575757c2a | ||
|
|
d7a2f77705 | ||
|
|
4a4b89606b | ||
|
|
1ebe2e7118 | ||
|
|
f1087e57bf | ||
|
|
250537ba58 | ||
|
|
ccc7222868 | ||
|
|
311a57a21e | ||
|
|
b7c45de6b1 | ||
|
|
c53c3cf181 | ||
|
|
a77d325c8a | ||
|
|
dd41384d82 | ||
|
|
aa55fb2d30 | ||
|
|
864f4efb8b | ||
|
|
83c5a8c24b | ||
|
|
57aef33fb3 | ||
|
|
6d78e5e640 | ||
|
|
f54a876f65 | ||
|
|
8bbae7926b | ||
|
|
db2b12021e | ||
|
|
8b2c9bf39d | ||
|
|
298bf8a1d4 | ||
|
|
77748571b4 | ||
|
|
8ce4c289c5 | ||
|
|
abbece8876 | ||
|
|
3ad40372e6 | ||
|
|
c6d0e7c926 | ||
|
|
bc015843d4 | ||
|
|
df64511feb | ||
|
|
db0981afe7 | ||
|
|
dcf6544f93 | ||
|
|
c2c8959fee | ||
|
|
ccb4fa26cd | ||
|
|
d0b5d752df | ||
|
|
4c63b630de | ||
|
|
9c0b62b4f5 | ||
|
|
e08eede5e2 | ||
|
|
05e53d4143 | ||
|
|
b2ea2b8b25 | ||
|
|
2245d8d710 | ||
|
|
d9424cc710 | ||
|
|
0503c141b7 | ||
|
|
1a6e4a00c7 | ||
|
|
42b7caae19 | ||
|
|
622e5fd71d | ||
|
|
59d773ea7e | ||
|
|
84bf5b4620 | ||
|
|
726c4d6c58 | ||
|
|
acc88d47a3 | ||
|
|
0a74594d09 | ||
|
|
d2f3cdd6de | ||
|
|
92d1ef8482 | ||
|
|
a8abc49632 | ||
|
|
d6ac340df0 | ||
|
|
c653330911 | ||
|
|
82d9b2fa87 | ||
|
|
3f96fad4f9 | ||
|
|
c2c4757a2b | ||
|
|
08b2d94ccd | ||
|
|
7fa61f0816 | ||
|
|
c65ba11ae6 | ||
|
|
b0651b13b5 | ||
|
|
a34521ec07 | ||
|
|
da47b90503 | ||
|
|
d27016a4e7 | ||
|
|
db99e2f68d | ||
|
|
cbb9117ab9 | ||
|
|
e2ccf011d9 | ||
|
|
17014bfad3 | ||
|
|
92b7166c10 | ||
|
|
7b464b8a49 | ||
|
|
5c765bf3e2 | ||
|
|
93619a9a37 | ||
|
|
a181cee6ae | ||
|
|
a0ade13f5a | ||
|
|
9823a56e1d | ||
|
|
3aeab73740 | ||
|
|
9801eebc58 | ||
|
|
11c622230c | ||
|
|
f0349488ed | ||
|
|
c85be323f5 | ||
|
|
6954c4df1b | ||
|
|
30a1595f72 | ||
|
|
f841a7ca76 | ||
|
|
07a004b301 | ||
|
|
b05582de39 | ||
|
|
fa7c4d19f0 | ||
|
|
1913eac5ed | ||
|
|
066346faa2 | ||
|
|
0a03355ceb | ||
|
|
43e24c5ae6 | ||
|
|
89d111a5f6 | ||
|
|
e1b5e68904 | ||
|
|
065ab2ebc2 | ||
|
|
20c95a4993 | ||
|
|
c40b488cb4 | ||
|
|
585e7c59e8 | ||
|
|
7077c47551 | ||
|
|
f789b2535a | ||
|
|
2bfc6917e2 | ||
|
|
d74ae19523 | ||
|
|
1c7c23ac73 | ||
|
|
86e8722cd8 | ||
|
|
53127daad8 | ||
|
|
91b20b7482 | ||
|
|
7a61602738 | ||
|
|
dd4b47d8b1 | ||
|
|
eb200b4687 | ||
|
|
0fc080d222 | ||
|
|
5da63602b3 | ||
|
|
457ff3ef48 | ||
|
|
0dbdecef10 | ||
|
|
b13b0e9492 | ||
|
|
b9fdfd9e36 | ||
|
|
4d6352db14 | ||
|
|
9eb82c6497 | ||
|
|
89cc89dfdf | ||
|
|
2b0f6597a4 | ||
|
|
294def205f | ||
|
|
34fa703466 | ||
|
|
ab3f8dcbfd | ||
|
|
02c20477b9 | ||
|
|
d9a4ee126a | ||
|
|
5b31cc4266 | ||
|
|
9bb1250869 | ||
|
|
94d6d19fb4 | ||
|
|
d43c070007 | ||
|
|
9ef236601b | ||
|
|
9d6f961d2b | ||
|
|
5453840950 | ||
|
|
d453964546 | ||
|
|
748a932811 | ||
|
|
8217ad4722 | ||
|
|
cc5a38ec5a | ||
|
|
6ef3fbbf76 | ||
|
|
76fd12c7cd | ||
|
|
9ecb6e50e4 | ||
|
|
ce9d0afb8a | ||
|
|
c39ea807e8 | ||
|
|
1e35339389 | ||
|
|
304797b099 | ||
|
|
b9bdb775ab | ||
|
|
b0909b8f5d | ||
|
|
5d278dacf1 | ||
|
|
df93858b4b | ||
|
|
e8da84fb9e | ||
|
|
7e90e067af | ||
|
|
5e4e917de5 | ||
|
|
05dc0212e7 | ||
|
|
c00052cbfb | ||
|
|
3d54a83822 | ||
|
|
fafd5edbda | ||
|
|
8478262580 | ||
|
|
83b1fee062 | ||
|
|
0fbbc896b2 | ||
|
|
ce1aaec31d | ||
|
|
0544b08543 | ||
|
|
1809852a0d | ||
|
|
88d2ee4813 | ||
|
|
bef6edb942 | ||
|
|
99f27de934 | ||
|
|
9c91f3a7bd | ||
|
|
1b6fed2ffd | ||
|
|
0721f2c1b7 | ||
|
|
b9a35da118 | ||
|
|
60f80056b1 | ||
|
|
540c3b79aa | ||
|
|
77be2e4fdf | ||
|
|
16f529e1d4 | ||
|
|
ebfcf2a9ef | ||
|
|
67a8138b65 | ||
|
|
e91863de59 | ||
|
|
44b457c191 | ||
|
|
a0ec0b6f9d | ||
|
|
1ec54abdc4 | ||
|
|
5efc4aa066 | ||
|
|
847c0b9644 |
@@ -1,13 +1,19 @@
|
|||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 1.4.0a1
|
current_version = 1.7.0a1
|
||||||
parse = (?P<major>\d+)
|
parse = (?P<major>[\d]+) # major version number
|
||||||
\.(?P<minor>\d+)
|
\.(?P<minor>[\d]+) # minor version number
|
||||||
\.(?P<patch>\d+)
|
\.(?P<patch>[\d]+) # patch version number
|
||||||
((?P<prekind>a|b|rc)
|
(?P<prerelease> # optional pre-release - ex: a1, b2, rc25
|
||||||
(?P<pre>\d+) # pre-release version num
|
(?P<prekind>a|b|rc) # pre-release type
|
||||||
|
(?P<num>[\d]+) # pre-release version number
|
||||||
)?
|
)?
|
||||||
|
( # optional nightly release indicator
|
||||||
|
\.(?P<nightly>dev[0-9]+) # ex: .dev02142023
|
||||||
|
)? # expected matches: `1.15.0`, `1.5.0a11`, `1.5.0a1.dev123`, `1.5.0.dev123457`, expected failures: `1`, `1.5`, `1.5.2-a1`, `text1.5.0`
|
||||||
serialize =
|
serialize =
|
||||||
{major}.{minor}.{patch}{prekind}{pre}
|
{major}.{minor}.{patch}{prekind}{num}.{nightly}
|
||||||
|
{major}.{minor}.{patch}.{nightly}
|
||||||
|
{major}.{minor}.{patch}{prekind}{num}
|
||||||
{major}.{minor}.{patch}
|
{major}.{minor}.{patch}
|
||||||
commit = False
|
commit = False
|
||||||
tag = False
|
tag = False
|
||||||
@@ -21,9 +27,11 @@ values =
|
|||||||
rc
|
rc
|
||||||
final
|
final
|
||||||
|
|
||||||
[bumpversion:part:pre]
|
[bumpversion:part:num]
|
||||||
first_value = 1
|
first_value = 1
|
||||||
|
|
||||||
|
[bumpversion:part:nightly]
|
||||||
|
|
||||||
[bumpversion:file:core/setup.py]
|
[bumpversion:file:core/setup.py]
|
||||||
|
|
||||||
[bumpversion:file:core/dbt/version.py]
|
[bumpversion:file:core/dbt/version.py]
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
For information on prior major and minor releases, see their changelogs:
|
For information on prior major and minor releases, see their changelogs:
|
||||||
|
|
||||||
|
|
||||||
|
* [1.6](https://github.com/dbt-labs/dbt-core/blob/1.6.latest/CHANGELOG.md)
|
||||||
|
* [1.5](https://github.com/dbt-labs/dbt-core/blob/1.5.latest/CHANGELOG.md)
|
||||||
|
* [1.4](https://github.com/dbt-labs/dbt-core/blob/1.4.latest/CHANGELOG.md)
|
||||||
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
||||||
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
||||||
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: "Dependency"
|
|
||||||
body: "Update pathspec requirement from ~=0.9.0 to >=0.9,<0.11 in /core"
|
|
||||||
time: 2022-09-23T00:06:46.00000Z
|
|
||||||
custom:
|
|
||||||
Author: dependabot[bot]
|
|
||||||
Issue: 4904
|
|
||||||
PR: 5917
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: "Dependency"
|
|
||||||
body: "Bump black from 22.8.0 to 22.10.0"
|
|
||||||
time: 2022-10-07T00:08:48.00000Z
|
|
||||||
custom:
|
|
||||||
Author: dependabot[bot]
|
|
||||||
Issue: 4904
|
|
||||||
PR: 6019
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: "Dependency"
|
|
||||||
body: "Bump mashumaro[msgpack] from 3.0.4 to 3.1.1 in /core"
|
|
||||||
time: 2022-10-20T00:07:53.00000Z
|
|
||||||
custom:
|
|
||||||
Author: dependabot[bot]
|
|
||||||
Issue: 4904
|
|
||||||
PR: 6108
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: "Dependency"
|
|
||||||
body: "Update colorama requirement from <0.4.6,>=0.3.9 to >=0.3.9,<0.4.7 in /core"
|
|
||||||
time: 2022-10-26T00:09:10.00000Z
|
|
||||||
custom:
|
|
||||||
Author: dependabot[bot]
|
|
||||||
Issue: 4904
|
|
||||||
PR: 6144
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
body: minor doc correction
|
|
||||||
time: 2022-09-08T15:41:57.689162-04:00
|
|
||||||
custom:
|
|
||||||
Author: andy-clapson
|
|
||||||
Issue: "5791"
|
|
||||||
PR: "5684"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
body: Generate API docs for new CLI interface
|
|
||||||
time: 2022-10-07T09:06:56.446078-05:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5528"
|
|
||||||
PR: "6022"
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
time: 2022-10-17T17:14:11.715348-05:00
|
|
||||||
custom:
|
|
||||||
Author: paulbenschmidt
|
|
||||||
Issue: "5880"
|
|
||||||
PR: "324"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Docs
|
|
||||||
body: Fix rendering of sample code for metrics
|
|
||||||
time: 2022-11-16T15:57:43.204201+01:00
|
|
||||||
custom:
|
|
||||||
Author: jtcohen6
|
|
||||||
Issue: "323"
|
|
||||||
PR: "346"
|
|
||||||
6
.changes/unreleased/Docs-20230718-192422.yaml
Normal file
6
.changes/unreleased/Docs-20230718-192422.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Docs
|
||||||
|
body: Remove static SQL codeblock for metrics
|
||||||
|
time: 2023-07-18T19:24:22.155323+02:00
|
||||||
|
custom:
|
||||||
|
Author: marcodamore
|
||||||
|
Issue: "436"
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Added favor-state flag to optionally favor state nodes even if unselected node
|
|
||||||
exists
|
|
||||||
time: 2022-04-08T16:54:59.696564+01:00
|
|
||||||
custom:
|
|
||||||
Author: daniel-murray josephberni
|
|
||||||
Issue: "2968"
|
|
||||||
PR: "5859"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Proto logging messages
|
|
||||||
time: 2022-08-17T15:48:57.225267-04:00
|
|
||||||
custom:
|
|
||||||
Author: gshank
|
|
||||||
Issue: "5610"
|
|
||||||
PR: "5643"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Friendlier error messages when packages.yml is malformed
|
|
||||||
time: 2022-09-12T12:59:35.121188+01:00
|
|
||||||
custom:
|
|
||||||
Author: jared-rimmer
|
|
||||||
Issue: "5486"
|
|
||||||
PR: "5812"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Migrate dbt-utils current_timestamp macros into core + adapters
|
|
||||||
time: 2022-09-14T09:56:25.97818-07:00
|
|
||||||
custom:
|
|
||||||
Author: colin-rogers-dbt
|
|
||||||
Issue: "5521"
|
|
||||||
PR: "5838"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Allow partitions in external tables to be supplied as a list
|
|
||||||
time: 2022-09-25T21:16:51.051239654+02:00
|
|
||||||
custom:
|
|
||||||
Author: pgoslatara
|
|
||||||
Issue: "5929"
|
|
||||||
PR: "5930"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: extend -f flag shorthand for seed command
|
|
||||||
time: 2022-10-03T11:07:05.381632-05:00
|
|
||||||
custom:
|
|
||||||
Author: dave-connors-3
|
|
||||||
Issue: "5990"
|
|
||||||
PR: "5991"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: This pulls the profile name from args when constructing a RuntimeConfig in lib.py,
|
|
||||||
enabling the dbt-server to override the value that's in the dbt_project.yml
|
|
||||||
time: 2022-11-02T15:00:03.000805-05:00
|
|
||||||
custom:
|
|
||||||
Author: racheldaniel
|
|
||||||
Issue: "6201"
|
|
||||||
PR: "6202"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Added an md5 function to the base context
|
|
||||||
time: 2022-11-14T18:52:07.788593+02:00
|
|
||||||
custom:
|
|
||||||
Author: haritamar
|
|
||||||
Issue: "6246"
|
|
||||||
PR: "6247"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Features
|
|
||||||
body: Exposures support metrics in lineage
|
|
||||||
time: 2022-11-30T11:29:13.256034-05:00
|
|
||||||
custom:
|
|
||||||
Author: michelleark
|
|
||||||
Issue: "6057"
|
|
||||||
PR: "6342"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Account for disabled flags on models in schema files more completely
|
|
||||||
time: 2022-09-16T10:48:54.162273-05:00
|
|
||||||
custom:
|
|
||||||
Author: emmyoop
|
|
||||||
Issue: "3992"
|
|
||||||
PR: "5868"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Add validation of enabled config for metrics, exposures and sources
|
|
||||||
time: 2022-10-10T11:32:18.752322-05:00
|
|
||||||
custom:
|
|
||||||
Author: emmyoop
|
|
||||||
Issue: "6030"
|
|
||||||
PR: "6038"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: check length of args of python model function before accessing it
|
|
||||||
time: 2022-10-11T16:07:15.464093-04:00
|
|
||||||
custom:
|
|
||||||
Author: chamini2
|
|
||||||
Issue: "6041"
|
|
||||||
PR: "6042"
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Add functors to ensure event types with str-type attributes are initialized
|
|
||||||
to spec, even when provided non-str type params.
|
|
||||||
time: 2022-10-16T17:37:42.846683-07:00
|
|
||||||
custom:
|
|
||||||
Author: versusfacit
|
|
||||||
Issue: "5436"
|
|
||||||
PR: "5874"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Allow hooks to fail without halting execution flow
|
|
||||||
time: 2022-11-07T09:53:14.340257-06:00
|
|
||||||
custom:
|
|
||||||
Author: ChenyuLInx
|
|
||||||
Issue: "5625"
|
|
||||||
PR: "6059"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Fixes
|
|
||||||
body: Clarify Error Message for how many models are allowed in a Python file
|
|
||||||
time: 2022-11-15T08:10:21.527884-05:00
|
|
||||||
custom:
|
|
||||||
Author: justbldwn
|
|
||||||
Issue: "6245"
|
|
||||||
PR: "6251"
|
|
||||||
6
.changes/unreleased/Fixes-20230718-125518.yaml
Normal file
6
.changes/unreleased/Fixes-20230718-125518.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
kind: Fixes
|
||||||
|
body: Enable converting deprecation warnings to errors
|
||||||
|
time: 2023-07-18T12:55:18.03914-04:00
|
||||||
|
custom:
|
||||||
|
Author: michelleark
|
||||||
|
Issue: "8130"
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Put black config in explicit config
|
|
||||||
time: 2022-09-27T19:42:59.241433-07:00
|
|
||||||
custom:
|
|
||||||
Author: max-sixty
|
|
||||||
Issue: "5946"
|
|
||||||
PR: "5947"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Added flat_graph attribute the Manifest class's deepcopy() coverage
|
|
||||||
time: 2022-09-29T13:44:06.275941-04:00
|
|
||||||
custom:
|
|
||||||
Author: peterallenwebb
|
|
||||||
Issue: "5809"
|
|
||||||
PR: "5975"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Add mypy configs so `mypy` passes from CLI
|
|
||||||
time: 2022-10-05T12:03:10.061263-07:00
|
|
||||||
custom:
|
|
||||||
Author: max-sixty
|
|
||||||
Issue: "5983"
|
|
||||||
PR: "5983"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Exception message cleanup.
|
|
||||||
time: 2022-10-07T09:46:27.682872-05:00
|
|
||||||
custom:
|
|
||||||
Author: emmyoop
|
|
||||||
Issue: "6023"
|
|
||||||
PR: "6024"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Add dmypy cache to gitignore
|
|
||||||
time: 2022-10-07T14:00:44.227644-07:00
|
|
||||||
custom:
|
|
||||||
Author: max-sixty
|
|
||||||
Issue: "6028"
|
|
||||||
PR: "5978"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Provide useful errors when the value of 'materialized' is invalid
|
|
||||||
time: 2022-10-13T18:19:12.167548-04:00
|
|
||||||
custom:
|
|
||||||
Author: peterallenwebb
|
|
||||||
Issue: "5229"
|
|
||||||
PR: "6025"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Fixed extra whitespace in strings introduced by black.
|
|
||||||
time: 2022-10-17T15:15:11.499246-05:00
|
|
||||||
custom:
|
|
||||||
Author: luke-bassett
|
|
||||||
Issue: "1350"
|
|
||||||
PR: "6086"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Clean up string formatting
|
|
||||||
time: 2022-10-17T15:58:44.676549-04:00
|
|
||||||
custom:
|
|
||||||
Author: eve-johns
|
|
||||||
Issue: "6068"
|
|
||||||
PR: "6082"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Remove the 'root_path' field from most nodes
|
|
||||||
time: 2022-10-28T10:48:37.687886-04:00
|
|
||||||
custom:
|
|
||||||
Author: gshank
|
|
||||||
Issue: "6171"
|
|
||||||
PR: "6172"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Combine certain logging events with different levels
|
|
||||||
time: 2022-10-28T11:03:44.887836-04:00
|
|
||||||
custom:
|
|
||||||
Author: gshank
|
|
||||||
Issue: "6173"
|
|
||||||
PR: "6174"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Convert threading tests to pytest
|
|
||||||
time: 2022-11-08T07:45:50.589147-06:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5942"
|
|
||||||
PR: "6226"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Convert postgres index tests to pytest
|
|
||||||
time: 2022-11-08T11:56:33.743042-06:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5770"
|
|
||||||
PR: "6228"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Convert use color tests to pytest
|
|
||||||
time: 2022-11-08T13:31:04.788547-06:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5771"
|
|
||||||
PR: "6230"
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
kind: Under the Hood
|
|
||||||
body: Add github actions workflow to generate high level CLI API docs
|
|
||||||
time: 2022-11-16T13:00:37.916202-06:00
|
|
||||||
custom:
|
|
||||||
Author: stu-k
|
|
||||||
Issue: "5942"
|
|
||||||
PR: "6187"
|
|
||||||
124
.changie.yaml
124
.changie.yaml
@@ -4,21 +4,70 @@ headerPath: header.tpl.md
|
|||||||
versionHeaderPath: ""
|
versionHeaderPath: ""
|
||||||
changelogPath: CHANGELOG.md
|
changelogPath: CHANGELOG.md
|
||||||
versionExt: md
|
versionExt: md
|
||||||
|
envPrefix: "CHANGIE_"
|
||||||
versionFormat: '## dbt-core {{.Version}} - {{.Time.Format "January 02, 2006"}}'
|
versionFormat: '## dbt-core {{.Version}} - {{.Time.Format "January 02, 2006"}}'
|
||||||
kindFormat: '### {{.Kind}}'
|
kindFormat: '### {{.Kind}}'
|
||||||
changeFormat: '- {{.Body}} ([#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-core/issues/{{.Custom.Issue}}), [#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-core/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $IssueList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.Issue }}
|
||||||
|
{{- range $issueNbr := $changes }}
|
||||||
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/issues/nbr)" | replace "nbr" $issueNbr }}
|
||||||
|
{{- $IssueList = append $IssueList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $IssueList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
|
|
||||||
kinds:
|
kinds:
|
||||||
- label: Breaking Changes
|
- label: Breaking Changes
|
||||||
- label: Features
|
- label: Features
|
||||||
- label: Fixes
|
- label: Fixes
|
||||||
- label: Docs
|
- label: Docs
|
||||||
changeFormat: '- {{.Body}} ([dbt-docs/#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-docs/issues/{{.Custom.Issue}}), [dbt-docs/#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-docs/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $IssueList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.Issue }}
|
||||||
|
{{- range $issueNbr := $changes }}
|
||||||
|
{{- $changeLink := "[dbt-docs/#nbr](https://github.com/dbt-labs/dbt-docs/issues/nbr)" | replace "nbr" $issueNbr }}
|
||||||
|
{{- $IssueList = append $IssueList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $IssueList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
- label: Under the Hood
|
- label: Under the Hood
|
||||||
- label: Dependencies
|
- label: Dependencies
|
||||||
changeFormat: '- {{.Body}} ({{if ne .Custom.Issue ""}}[#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-core/issues/{{.Custom.Issue}}), {{end}}[#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-core/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $PRList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.PR }}
|
||||||
|
{{- range $pullrequest := $changes }}
|
||||||
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/pull/nbr)" | replace "nbr" $pullrequest }}
|
||||||
|
{{- $PRList = append $PRList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $PRList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
|
skipGlobalChoices: true
|
||||||
|
additionalChoices:
|
||||||
|
- key: Author
|
||||||
|
label: GitHub Username(s) (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 3
|
||||||
|
- key: PR
|
||||||
|
label: GitHub Pull Request Number (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
- label: Security
|
- label: Security
|
||||||
changeFormat: '- {{.Body}} ({{if ne .Custom.Issue ""}}[#{{.Custom.Issue}}](https://github.com/dbt-labs/dbt-core/issues/{{.Custom.Issue}}), {{end}}[#{{.Custom.PR}}](https://github.com/dbt-labs/dbt-core/pull/{{.Custom.PR}}))'
|
changeFormat: |-
|
||||||
|
{{- $PRList := list }}
|
||||||
|
{{- $changes := splitList " " $.Custom.PR }}
|
||||||
|
{{- range $pullrequest := $changes }}
|
||||||
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/pull/nbr)" | replace "nbr" $pullrequest }}
|
||||||
|
{{- $PRList = append $PRList $changeLink }}
|
||||||
|
{{- end -}}
|
||||||
|
- {{.Body}} ({{ range $index, $element := $PRList }}{{if $index}}, {{end}}{{$element}}{{end}})
|
||||||
|
skipGlobalChoices: true
|
||||||
|
additionalChoices:
|
||||||
|
- key: Author
|
||||||
|
label: GitHub Username(s) (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 3
|
||||||
|
- key: PR
|
||||||
|
label: GitHub Pull Request Number (separated by a single space if multiple)
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
|
||||||
newlines:
|
newlines:
|
||||||
afterChangelogHeader: 1
|
afterChangelogHeader: 1
|
||||||
@@ -33,42 +82,53 @@ custom:
|
|||||||
type: string
|
type: string
|
||||||
minLength: 3
|
minLength: 3
|
||||||
- key: Issue
|
- key: Issue
|
||||||
label: GitHub Issue Number
|
label: GitHub Issue Number (separated by a single space if multiple)
|
||||||
type: int
|
type: string
|
||||||
minInt: 1
|
minLength: 1
|
||||||
- key: PR
|
|
||||||
label: GitHub Pull Request Number
|
|
||||||
type: int
|
|
||||||
minInt: 1
|
|
||||||
|
|
||||||
footerFormat: |
|
footerFormat: |
|
||||||
{{- $contributorDict := dict }}
|
{{- $contributorDict := dict }}
|
||||||
{{- /* any names added to this list should be all lowercase for later matching purposes */}}
|
{{- /* ensure all names in this list are all lowercase for later matching purposes */}}
|
||||||
{{- $core_team := list "michelleark" "peterallenwebb" "emmyoop" "nathaniel-may" "gshank" "leahwicz" "chenyulinx" "stu-k" "iknox-fa" "versusfacit" "mcknight-42" "jtcohen6" "dependabot[bot]" "snyk-bot" "colin-rogers-dbt" }}
|
{{- $core_team := splitList " " .Env.CORE_TEAM }}
|
||||||
|
{{- /* ensure we always skip snyk and dependabot in addition to the core team */}}
|
||||||
|
{{- $maintainers := list "dependabot[bot]" "snyk-bot"}}
|
||||||
|
{{- range $team_member := $core_team }}
|
||||||
|
{{- $team_member_lower := lower $team_member }}
|
||||||
|
{{- $maintainers = append $maintainers $team_member_lower }}
|
||||||
|
{{- end }}
|
||||||
{{- range $change := .Changes }}
|
{{- range $change := .Changes }}
|
||||||
{{- $authorList := splitList " " $change.Custom.Author }}
|
{{- $authorList := splitList " " $change.Custom.Author }}
|
||||||
{{- /* loop through all authors for a PR */}}
|
{{- /* loop through all authors for a single changelog */}}
|
||||||
{{- range $author := $authorList }}
|
{{- range $author := $authorList }}
|
||||||
{{- $authorLower := lower $author }}
|
{{- $authorLower := lower $author }}
|
||||||
{{- /* we only want to include non-core team contributors */}}
|
{{- /* we only want to include non-core team contributors */}}
|
||||||
{{- if not (has $authorLower $core_team)}}
|
{{- if not (has $authorLower $maintainers)}}
|
||||||
{{- /* Docs kind link back to dbt-docs instead of dbt-core PRs */}}
|
{{- $changeList := splitList " " $change.Custom.Author }}
|
||||||
{{- $prLink := $change.Kind }}
|
{{- $IssueList := list }}
|
||||||
{{- if eq $change.Kind "Docs" }}
|
{{- $changeLink := $change.Kind }}
|
||||||
{{- $prLink = "[dbt-docs/#pr](https://github.com/dbt-labs/dbt-docs/pull/pr)" | replace "pr" $change.Custom.PR }}
|
{{- if or (eq $change.Kind "Dependencies") (eq $change.Kind "Security") }}
|
||||||
{{- else }}
|
{{- $changes := splitList " " $change.Custom.PR }}
|
||||||
{{- $prLink = "[#pr](https://github.com/dbt-labs/dbt-core/pull/pr)" | replace "pr" $change.Custom.PR }}
|
{{- range $issueNbr := $changes }}
|
||||||
{{- end }}
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/pull/nbr)" | replace "nbr" $issueNbr }}
|
||||||
{{- /* check if this contributor has other PRs associated with them already */}}
|
{{- $IssueList = append $IssueList $changeLink }}
|
||||||
{{- if hasKey $contributorDict $author }}
|
{{- end -}}
|
||||||
{{- $prList := get $contributorDict $author }}
|
{{- else }}
|
||||||
{{- $prList = append $prList $prLink }}
|
{{- $changes := splitList " " $change.Custom.Issue }}
|
||||||
{{- $contributorDict := set $contributorDict $author $prList }}
|
{{- range $issueNbr := $changes }}
|
||||||
{{- else }}
|
{{- $changeLink := "[#nbr](https://github.com/dbt-labs/dbt-core/issues/nbr)" | replace "nbr" $issueNbr }}
|
||||||
{{- $prList := list $prLink }}
|
{{- $IssueList = append $IssueList $changeLink }}
|
||||||
{{- $contributorDict := set $contributorDict $author $prList }}
|
{{- end -}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end}}
|
{{- /* check if this contributor has other changes associated with them already */}}
|
||||||
|
{{- if hasKey $contributorDict $author }}
|
||||||
|
{{- $contributionList := get $contributorDict $author }}
|
||||||
|
{{- $contributionList = concat $contributionList $IssueList }}
|
||||||
|
{{- $contributorDict := set $contributorDict $author $contributionList }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $contributionList := $IssueList }}
|
||||||
|
{{- $contributorDict := set $contributorDict $author $contributionList }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end}}
|
||||||
{{- end}}
|
{{- end}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- /* no indentation here for formatting so the final markdown doesn't have unneeded indentations */}}
|
{{- /* no indentation here for formatting so the final markdown doesn't have unneeded indentations */}}
|
||||||
|
|||||||
2
.flake8
2
.flake8
@@ -9,4 +9,4 @@ ignore =
|
|||||||
E203 # makes Flake8 work like black
|
E203 # makes Flake8 work like black
|
||||||
E741
|
E741
|
||||||
E501 # long line checking is done in black
|
E501 # long line checking is done in black
|
||||||
exclude = test
|
exclude = test/
|
||||||
|
|||||||
6
.gitattributes
vendored
Normal file
6
.gitattributes
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
core/dbt/include/index.html binary
|
||||||
|
tests/functional/artifacts/data/state/*/manifest.json binary
|
||||||
|
core/dbt/docs/build/html/searchindex.js binary
|
||||||
|
core/dbt/docs/build/html/index.html binary
|
||||||
|
performance/runner/Cargo.lock binary
|
||||||
|
core/dbt/events/types_pb2.py binary
|
||||||
53
.github/CODEOWNERS
vendored
53
.github/CODEOWNERS
vendored
@@ -11,44 +11,24 @@
|
|||||||
|
|
||||||
# As a default for areas with no assignment,
|
# As a default for areas with no assignment,
|
||||||
# the core team as a whole will be assigned
|
# the core team as a whole will be assigned
|
||||||
* @dbt-labs/core
|
* @dbt-labs/core-team
|
||||||
|
|
||||||
# Changes to GitHub configurations including Actions
|
### OSS Tooling Guild
|
||||||
/.github/ @leahwicz
|
|
||||||
|
|
||||||
### LANGUAGE
|
/.github/ @dbt-labs/guild-oss-tooling
|
||||||
|
.bumpversion.cfg @dbt-labs/guild-oss-tooling
|
||||||
|
|
||||||
# Language core modules
|
.changie.yaml @dbt-labs/guild-oss-tooling
|
||||||
/core/dbt/config/ @dbt-labs/core-language
|
|
||||||
/core/dbt/context/ @dbt-labs/core-language
|
|
||||||
/core/dbt/contracts/ @dbt-labs/core-language
|
|
||||||
/core/dbt/deps/ @dbt-labs/core-language
|
|
||||||
/core/dbt/events/ @dbt-labs/core-language # structured logging
|
|
||||||
/core/dbt/parser/ @dbt-labs/core-language
|
|
||||||
|
|
||||||
# Language misc files
|
pre-commit-config.yaml @dbt-labs/guild-oss-tooling
|
||||||
/core/dbt/dataclass_schema.py @dbt-labs/core-language
|
pytest.ini @dbt-labs/guild-oss-tooling
|
||||||
/core/dbt/hooks.py @dbt-labs/core-language
|
tox.ini @dbt-labs/guild-oss-tooling
|
||||||
/core/dbt/node_types.py @dbt-labs/core-language
|
|
||||||
/core/dbt/semver.py @dbt-labs/core-language
|
|
||||||
|
|
||||||
|
|
||||||
### EXECUTION
|
|
||||||
|
|
||||||
# Execution core modules
|
|
||||||
/core/dbt/graph/ @dbt-labs/core-execution
|
|
||||||
/core/dbt/task/ @dbt-labs/core-execution
|
|
||||||
|
|
||||||
# Execution misc files
|
|
||||||
/core/dbt/compilation.py @dbt-labs/core-execution
|
|
||||||
/core/dbt/flags.py @dbt-labs/core-execution
|
|
||||||
/core/dbt/lib.py @dbt-labs/core-execution
|
|
||||||
/core/dbt/main.py @dbt-labs/core-execution
|
|
||||||
/core/dbt/profiler.py @dbt-labs/core-execution
|
|
||||||
/core/dbt/selected_resources.py @dbt-labs/core-execution
|
|
||||||
/core/dbt/tracking.py @dbt-labs/core-execution
|
|
||||||
/core/dbt/version.py @dbt-labs/core-execution
|
|
||||||
|
|
||||||
|
pyproject.toml @dbt-labs/guild-oss-tooling
|
||||||
|
requirements.txt @dbt-labs/guild-oss-tooling
|
||||||
|
dev_requirements.txt @dbt-labs/guild-oss-tooling
|
||||||
|
/core/setup.py @dbt-labs/guild-oss-tooling
|
||||||
|
/core/MANIFEST.in @dbt-labs/guild-oss-tooling
|
||||||
|
|
||||||
### ADAPTERS
|
### ADAPTERS
|
||||||
|
|
||||||
@@ -60,6 +40,7 @@
|
|||||||
|
|
||||||
# Postgres plugin
|
# Postgres plugin
|
||||||
/plugins/ @dbt-labs/core-adapters
|
/plugins/ @dbt-labs/core-adapters
|
||||||
|
/plugins/postgres/setup.py @dbt-labs/core-adapters @dbt-labs/guild-oss-tooling
|
||||||
|
|
||||||
# Functional tests for adapter plugins
|
# Functional tests for adapter plugins
|
||||||
/tests/adapter @dbt-labs/core-adapters
|
/tests/adapter @dbt-labs/core-adapters
|
||||||
@@ -71,5 +52,9 @@
|
|||||||
# Perf regression testing framework
|
# Perf regression testing framework
|
||||||
# This excludes the test project files itself since those aren't specific
|
# This excludes the test project files itself since those aren't specific
|
||||||
# framework changes (excluded by not setting an owner next to it- no owner)
|
# framework changes (excluded by not setting an owner next to it- no owner)
|
||||||
/performance @nathaniel-may
|
/performance @nathaniel-may
|
||||||
/performance/projects
|
/performance/projects
|
||||||
|
|
||||||
|
### ARTIFACTS
|
||||||
|
|
||||||
|
/schemas/dbt @dbt-labs/cloud-artifacts
|
||||||
|
|||||||
22
.github/_README.md
vendored
22
.github/_README.md
vendored
@@ -63,12 +63,12 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
```
|
```
|
||||||
|
|
||||||
### Secrets
|
### Secrets
|
||||||
- When to use a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) vs the [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) generated for the action?
|
- When to use a [Personal Access Token (PAT)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) vs the [GITHUB_TOKEN](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) generated for the action?
|
||||||
|
|
||||||
The `GITHUB_TOKEN` is used by default. In most cases it is sufficient for what you need.
|
The `GITHUB_TOKEN` is used by default. In most cases it is sufficient for what you need.
|
||||||
|
|
||||||
If you expect the workflow to result in a commit to that should retrigger workflows, you will need to use a Personal Access Token for the bot to commit the file. When using the GITHUB_TOKEN, the resulting commit will not trigger another GitHub Actions Workflow run. This is due to limitations set by GitHub. See [the docs](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) for a more detailed explanation.
|
If you expect the workflow to result in a commit to that should retrigger workflows, you will need to use a Personal Access Token for the bot to commit the file. When using the GITHUB_TOKEN, the resulting commit will not trigger another GitHub Actions Workflow run. This is due to limitations set by GitHub. See [the docs](https://docs.github.com/en/actions/security-guides/automatic-token-authentication#using-the-github_token-in-a-workflow) for a more detailed explanation.
|
||||||
|
|
||||||
For example, we must use a PAT in our workflow to commit a new changelog yaml file for bot PRs. Once the file has been committed to the branch, it should retrigger the check to validate that a changelog exists on the PR. Otherwise, it would stay in a failed state since the check would never retrigger.
|
For example, we must use a PAT in our workflow to commit a new changelog yaml file for bot PRs. Once the file has been committed to the branch, it should retrigger the check to validate that a changelog exists on the PR. Otherwise, it would stay in a failed state since the check would never retrigger.
|
||||||
@@ -105,7 +105,7 @@ Some triggers of note that we use:
|
|||||||
|
|
||||||
```
|
```
|
||||||
# **what?**
|
# **what?**
|
||||||
# Describe what the action does.
|
# Describe what the action does.
|
||||||
|
|
||||||
# **why?**
|
# **why?**
|
||||||
# Why does this action exist?
|
# Why does this action exist?
|
||||||
@@ -138,7 +138,7 @@ Some triggers of note that we use:
|
|||||||
id: fp
|
id: fp
|
||||||
run: |
|
run: |
|
||||||
FILEPATH=.changes/unreleased/Dependencies-${{ steps.filename_time.outputs.time }}.yaml
|
FILEPATH=.changes/unreleased/Dependencies-${{ steps.filename_time.outputs.time }}.yaml
|
||||||
echo "::set-output name=FILEPATH::$FILEPATH"
|
echo "FILEPATH=$FILEPATH" >> $GITHUB_OUTPUT
|
||||||
```
|
```
|
||||||
|
|
||||||
- Print out all variables you will reference as the first step of a job. This allows for easier debugging. The first job should log all inputs. Subsequent jobs should reference outputs of other jobs, if present.
|
- Print out all variables you will reference as the first step of a job. This allows for easier debugging. The first job should log all inputs. Subsequent jobs should reference outputs of other jobs, if present.
|
||||||
@@ -158,14 +158,14 @@ Some triggers of note that we use:
|
|||||||
echo "The build_script_path: ${{ inputs.build_script_path }}"
|
echo "The build_script_path: ${{ inputs.build_script_path }}"
|
||||||
echo "The s3_bucket_name: ${{ inputs.s3_bucket_name }}"
|
echo "The s3_bucket_name: ${{ inputs.s3_bucket_name }}"
|
||||||
echo "The package_test_command: ${{ inputs.package_test_command }}"
|
echo "The package_test_command: ${{ inputs.package_test_command }}"
|
||||||
|
|
||||||
# collect all the variables that need to be used in subsequent jobs
|
# collect all the variables that need to be used in subsequent jobs
|
||||||
- name: Set Variables
|
- name: Set Variables
|
||||||
id: variables
|
id: variables
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=important_path::'performance/runner/Cargo.toml'"
|
echo "important_path='performance/runner/Cargo.toml'" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=release_id::${{github.event.inputs.release_id}}"
|
echo "release_id=${{github.event.inputs.release_id}}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=open_prs::${{github.event.inputs.open_prs}}"
|
echo "open_prs=${{github.event.inputs.open_prs}}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
job2:
|
job2:
|
||||||
needs: [job1]
|
needs: [job1]
|
||||||
@@ -190,14 +190,14 @@ ___
|
|||||||
### Actions from the Marketplace
|
### Actions from the Marketplace
|
||||||
- Don’t use external actions for things that can easily be accomplished manually.
|
- Don’t use external actions for things that can easily be accomplished manually.
|
||||||
- Always read through what an external action does before using it! Often an action in the GitHub Actions Marketplace can be replaced with a few lines in bash. This is much more maintainable (and won’t change under us) and clear as to what’s actually happening. It also prevents any
|
- Always read through what an external action does before using it! Often an action in the GitHub Actions Marketplace can be replaced with a few lines in bash. This is much more maintainable (and won’t change under us) and clear as to what’s actually happening. It also prevents any
|
||||||
- Pin actions _we don't control_ to tags.
|
- Pin actions _we don't control_ to tags.
|
||||||
|
|
||||||
### Connecting to AWS
|
### Connecting to AWS
|
||||||
- Authenticate with the aws managed workflow
|
- Authenticate with the aws managed workflow
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Configure AWS credentials from Test account
|
- name: Configure AWS credentials from Test account
|
||||||
uses: aws-actions/configure-aws-credentials@v1
|
uses: aws-actions/configure-aws-credentials@v2
|
||||||
with:
|
with:
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
@@ -208,7 +208,7 @@ ___
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Copy Artifacts from S3 via CLI
|
- name: Copy Artifacts from S3 via CLI
|
||||||
run: aws s3 cp ${{ env.s3_bucket }} . --recursive
|
run: aws s3 cp ${{ env.s3_bucket }} . --recursive
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|||||||
2
.github/actions/latest-wrangler/README.md
vendored
2
.github/actions/latest-wrangler/README.md
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v3
|
||||||
- name: Wrangle latest tag
|
- name: Wrangle latest tag
|
||||||
id: is_latest
|
id: is_latest
|
||||||
uses: ./.github/actions/latest-wrangler
|
uses: ./.github/actions/latest-wrangler
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v3
|
||||||
- name: Wrangle latest tag
|
- name: Wrangle latest tag
|
||||||
id: is_latest
|
id: is_latest
|
||||||
uses: ./.github/actions/latest-wrangler
|
uses: ./.github/actions/latest-wrangler
|
||||||
|
|||||||
17
.github/actions/latest-wrangler/main.py
vendored
17
.github/actions/latest-wrangler/main.py
vendored
@@ -28,11 +28,12 @@ if __name__ == "__main__":
|
|||||||
if package_request.status_code == 404:
|
if package_request.status_code == 404:
|
||||||
if halt_on_missing:
|
if halt_on_missing:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
# everything is the latest if the package doesn't exist
|
||||||
# everything is the latest if the package doesn't exist
|
github_output = os.environ.get("GITHUB_OUTPUT")
|
||||||
print(f"::set-output name=latest::{True}")
|
with open(github_output, "at", encoding="utf-8") as gh_output:
|
||||||
print(f"::set-output name=minor_latest::{True}")
|
gh_output.write("latest=True")
|
||||||
sys.exit(0)
|
gh_output.write("minor_latest=True")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# TODO: verify package meta is "correct"
|
# TODO: verify package meta is "correct"
|
||||||
# https://github.com/dbt-labs/dbt-core/issues/4640
|
# https://github.com/dbt-labs/dbt-core/issues/4640
|
||||||
@@ -91,5 +92,7 @@ if __name__ == "__main__":
|
|||||||
latest = is_latest(pre_rel, new_version, current_latest)
|
latest = is_latest(pre_rel, new_version, current_latest)
|
||||||
minor_latest = is_latest(pre_rel, new_version, current_minor_latest)
|
minor_latest = is_latest(pre_rel, new_version, current_minor_latest)
|
||||||
|
|
||||||
print(f"::set-output name=latest::{latest}")
|
github_output = os.environ.get("GITHUB_OUTPUT")
|
||||||
print(f"::set-output name=minor_latest::{minor_latest}")
|
with open(github_output, "at", encoding="utf-8") as gh_output:
|
||||||
|
gh_output.write(f"latest={latest}")
|
||||||
|
gh_output.write(f"minor_latest={minor_latest}")
|
||||||
|
|||||||
30
.github/pull_request_template.md
vendored
30
.github/pull_request_template.md
vendored
@@ -1,23 +1,35 @@
|
|||||||
resolves #
|
resolves #
|
||||||
|
[docs](https://github.com/dbt-labs/docs.getdbt.com/issues/new/choose) dbt-labs/docs.getdbt.com/#
|
||||||
|
|
||||||
<!---
|
<!---
|
||||||
Include the number of the issue addressed by this PR above if applicable.
|
Include the number of the issue addressed by this PR above if applicable.
|
||||||
PRs for code changes without an associated issue *will not be merged*.
|
PRs for code changes without an associated issue *will not be merged*.
|
||||||
See CONTRIBUTING.md for more information.
|
See CONTRIBUTING.md for more information.
|
||||||
|
|
||||||
|
Include the number of the docs issue that was opened for this PR. If
|
||||||
|
this change has no user-facing implications, "N/A" suffices instead. New
|
||||||
|
docs tickets can be created by clicking the link above or by going to
|
||||||
|
https://github.com/dbt-labs/docs.getdbt.com/issues/new/choose.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Description
|
### Problem
|
||||||
|
|
||||||
<!---
|
<!---
|
||||||
Describe the Pull Request here. Add any references and info to help reviewers
|
Describe the problem this PR is solving. What is the application state
|
||||||
understand your changes. Include any tradeoffs you considered.
|
before this PR is merged?
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Describe the way this PR solves the above problem. Add as much detail as you
|
||||||
|
can to help reviewers understand your changes. Include any alternatives and
|
||||||
|
tradeoffs you considered.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Checklist
|
### Checklist
|
||||||
|
|
||||||
- [ ] I have read [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md) and understand what's expected of me
|
- [ ] I have read [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md) and understand what's expected of me
|
||||||
- [ ] I have signed the [CLA](https://docs.getdbt.com/docs/contributor-license-agreements)
|
- [ ] I have run this code in development and it appears to resolve the stated issue
|
||||||
- [ ] I have run this code in development and it appears to resolve the stated issue
|
|
||||||
- [ ] This PR includes tests, or tests are not required/relevant for this PR
|
- [ ] This PR includes tests, or tests are not required/relevant for this PR
|
||||||
- [ ] I have [opened an issue to add/update docs](https://github.com/dbt-labs/docs.getdbt.com/issues/new/choose), or docs changes are not required/relevant for this PR
|
- [ ] This PR has no interface changes (e.g. macros, cli, logs, json artifacts, config files, adapter interface, etc) or this PR has already received feedback and approval from Product or DX
|
||||||
- [ ] I have run `changie new` to [create a changelog entry](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md#adding-a-changelog-entry)
|
|
||||||
|
|||||||
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@@ -35,6 +35,6 @@ jobs:
|
|||||||
github.event.pull_request.merged
|
github.event.pull_request.merged
|
||||||
&& contains(github.event.label.name, 'backport')
|
&& contains(github.event.label.name, 'backport')
|
||||||
steps:
|
steps:
|
||||||
- uses: tibdex/backport@v2.0.2
|
- uses: tibdex/backport@v2.0.3
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
6
.github/workflows/bot-changelog.yml
vendored
6
.github/workflows/bot-changelog.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- label: "dependencies"
|
- label: "dependencies"
|
||||||
changie_kind: "Dependency"
|
changie_kind: "Dependencies"
|
||||||
- label: "snyk"
|
- label: "snyk"
|
||||||
changie_kind: "Security"
|
changie_kind: "Security"
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
- name: Create and commit changelog on bot PR
|
- name: Create and commit changelog on bot PR
|
||||||
if: ${{ contains(github.event.pull_request.labels.*.name, matrix.label) }}
|
if: ${{ contains(github.event.pull_request.labels.*.name, matrix.label) }}
|
||||||
id: bot_changelog
|
id: bot_changelog
|
||||||
uses: emmyoop/changie_bot@v1.0.1
|
uses: emmyoop/changie_bot@v1.1.0
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.FISHTOWN_BOT_PAT }}
|
GITHUB_TOKEN: ${{ secrets.FISHTOWN_BOT_PAT }}
|
||||||
commit_author_name: "Github Build Bot"
|
commit_author_name: "Github Build Bot"
|
||||||
@@ -58,4 +58,4 @@ jobs:
|
|||||||
commit_message: "Add automated changelog yaml from template for bot PR"
|
commit_message: "Add automated changelog yaml from template for bot PR"
|
||||||
changie_kind: ${{ matrix.changie_kind }}
|
changie_kind: ${{ matrix.changie_kind }}
|
||||||
label: ${{ matrix.label }}
|
label: ${{ matrix.label }}
|
||||||
custom_changelog_string: "custom:\n Author: ${{ github.event.pull_request.user.login }}\n Issue: 4904\n PR: ${{ github.event.pull_request.number }}"
|
custom_changelog_string: "custom:\n Author: ${{ github.event.pull_request.user.login }}\n PR: ${{ github.event.pull_request.number }}"
|
||||||
|
|||||||
41
.github/workflows/cut-release-branch.yml
vendored
Normal file
41
.github/workflows/cut-release-branch.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# **what?**
|
||||||
|
# Cuts a new `*.latest` branch
|
||||||
|
# Also cleans up all files in `.changes/unreleased` and `.changes/previous verion on
|
||||||
|
# `main` and bumps `main` to the input version.
|
||||||
|
|
||||||
|
# **why?**
|
||||||
|
# Generally reduces the workload of engineers and reduces error. Allow automation.
|
||||||
|
|
||||||
|
# **when?**
|
||||||
|
# This will run when called manually.
|
||||||
|
|
||||||
|
name: Cut new release branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version_to_bump_main:
|
||||||
|
description: 'The alpha version main should bump to (ex. 1.6.0a1)'
|
||||||
|
required: true
|
||||||
|
new_branch_name:
|
||||||
|
description: 'The full name of the new branch (ex. 1.5.latest)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cut_branch:
|
||||||
|
name: "Cut branch and clean up main for dbt-core"
|
||||||
|
uses: dbt-labs/actions/.github/workflows/cut-release-branch.yml@main
|
||||||
|
with:
|
||||||
|
version_to_bump_main: ${{ inputs.version_to_bump_main }}
|
||||||
|
new_branch_name: ${{ inputs.new_branch_name }}
|
||||||
|
PR_title: "Cleanup main after cutting new ${{ inputs.new_branch_name }} branch"
|
||||||
|
PR_body: "All adapter PRs will fail CI until the dbt-core PR has been merged due to release version conflicts."
|
||||||
|
secrets:
|
||||||
|
FISHTOWN_BOT_PAT: ${{ secrets.FISHTOWN_BOT_PAT }}
|
||||||
166
.github/workflows/generate-cli-api-docs.yml
vendored
166
.github/workflows/generate-cli-api-docs.yml
vendored
@@ -1,166 +0,0 @@
|
|||||||
# **what?**
|
|
||||||
# On push, if anything in core/dbt/docs or core/dbt/cli has been
|
|
||||||
# created or modified, regenerate the CLI API docs using sphinx.
|
|
||||||
|
|
||||||
# **why?**
|
|
||||||
# We watch for changes in core/dbt/cli because the CLI API docs rely on click
|
|
||||||
# and all supporting flags/params to be generated. We watch for changes in
|
|
||||||
# core/dbt/docs since any changes to sphinx configuration or any of the
|
|
||||||
# .rst files there could result in a differently build final index.html file.
|
|
||||||
|
|
||||||
# **when?**
|
|
||||||
# Whenever a change has been pushed to a branch, and only if there is a diff
|
|
||||||
# between the PR branch and main's core/dbt/cli and or core/dbt/docs dirs.
|
|
||||||
|
|
||||||
# TODO: add bot comment to PR informing contributor that the docs have been committed
|
|
||||||
# TODO: figure out why github action triggered pushes cause github to fail to report
|
|
||||||
# the status of jobs
|
|
||||||
|
|
||||||
name: Generate CLI API docs
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
CLI_DIR: ${{ github.workspace }}/core/dbt/cli
|
|
||||||
DOCS_DIR: ${{ github.workspace }}/core/dbt/docs
|
|
||||||
DOCS_BUILD_DIR: ${{ github.workspace }}/core/dbt/docs/build
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
check_gen:
|
|
||||||
name: check if generation needed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
cli_dir_changed: ${{ steps.check_cli.outputs.cli_dir_changed }}
|
|
||||||
docs_dir_changed: ${{ steps.check_docs.outputs.docs_dir_changed }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "[DEBUG] print variables"
|
|
||||||
run: |
|
|
||||||
echo "env.CLI_DIR: ${{ env.CLI_DIR }}"
|
|
||||||
echo "env.DOCS_BUILD_DIR: ${{ env.DOCS_BUILD_DIR }}"
|
|
||||||
echo "env.DOCS_DIR: ${{ env.DOCS_DIR }}"
|
|
||||||
echo ">>>>> git log"
|
|
||||||
git log --pretty=oneline | head -5
|
|
||||||
|
|
||||||
- name: git checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ github.head_ref }}
|
|
||||||
|
|
||||||
- name: set shas
|
|
||||||
id: set_shas
|
|
||||||
run: |
|
|
||||||
THIS_SHA=$(git rev-parse @)
|
|
||||||
LAST_SHA=$(git rev-parse @~1)
|
|
||||||
|
|
||||||
echo "this sha: $THIS_SHA"
|
|
||||||
echo "last sha: $LAST_SHA"
|
|
||||||
|
|
||||||
echo "this_sha=$THIS_SHA" >> $GITHUB_OUTPUT
|
|
||||||
echo "last_sha=$LAST_SHA" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: check for changes in core/dbt/cli
|
|
||||||
id: check_cli
|
|
||||||
run: |
|
|
||||||
CLI_DIR_CHANGES=$(git diff \
|
|
||||||
${{ steps.set_shas.outputs.last_sha }} \
|
|
||||||
${{ steps.set_shas.outputs.this_sha }} \
|
|
||||||
-- ${{ env.CLI_DIR }})
|
|
||||||
|
|
||||||
if [ -n "$CLI_DIR_CHANGES" ]; then
|
|
||||||
echo "changes found"
|
|
||||||
echo $CLI_DIR_CHANGES
|
|
||||||
echo "cli_dir_changed=true" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "cli_dir_changed=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "no changes found"
|
|
||||||
|
|
||||||
- name: check for changes in core/dbt/docs
|
|
||||||
id: check_docs
|
|
||||||
if: steps.check_cli.outputs.cli_dir_changed == 'false'
|
|
||||||
run: |
|
|
||||||
DOCS_DIR_CHANGES=$(git diff --name-only \
|
|
||||||
${{ steps.set_shas.outputs.last_sha }} \
|
|
||||||
${{ steps.set_shas.outputs.this_sha }} \
|
|
||||||
-- ${{ env.DOCS_DIR }} ':!${{ env.DOCS_BUILD_DIR }}')
|
|
||||||
|
|
||||||
DOCS_BUILD_DIR_CHANGES=$(git diff --name-only \
|
|
||||||
${{ steps.set_shas.outputs.last_sha }} \
|
|
||||||
${{ steps.set_shas.outputs.this_sha }} \
|
|
||||||
-- ${{ env.DOCS_BUILD_DIR }})
|
|
||||||
|
|
||||||
if [ -n "$DOCS_DIR_CHANGES" ] && [ -z "$DOCS_BUILD_DIR_CHANGES" ]; then
|
|
||||||
echo "changes found"
|
|
||||||
echo $DOCS_DIR_CHANGES
|
|
||||||
echo "docs_dir_changed=true" >> $GITHUB_OUTPUT
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "docs_dir_changed=false" >> $GITHUB_OUTPUT
|
|
||||||
echo "no changes found"
|
|
||||||
|
|
||||||
gen_docs:
|
|
||||||
name: generate docs
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [check_gen]
|
|
||||||
if: |
|
|
||||||
needs.check_gen.outputs.cli_dir_changed == 'true'
|
|
||||||
|| needs.check_gen.outputs.docs_dir_changed == 'true'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: "[DEBUG] print variables"
|
|
||||||
run: |
|
|
||||||
echo "env.DOCS_DIR: ${{ env.DOCS_DIR }}"
|
|
||||||
echo "github head_ref: ${{ github.head_ref }}"
|
|
||||||
|
|
||||||
- name: git checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
ref: ${{ github.head_ref }}
|
|
||||||
|
|
||||||
- name: install python
|
|
||||||
uses: actions/setup-python@v4.3.0
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
|
|
||||||
- name: install dev requirements
|
|
||||||
run: |
|
|
||||||
python3 -m venv env
|
|
||||||
source env/bin/activate
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install -r requirements.txt -r dev-requirements.txt
|
|
||||||
|
|
||||||
- name: generate docs
|
|
||||||
run: |
|
|
||||||
source env/bin/activate
|
|
||||||
cd ${{ env.DOCS_DIR }}
|
|
||||||
|
|
||||||
echo "cleaning existing docs"
|
|
||||||
make clean
|
|
||||||
|
|
||||||
echo "creating docs"
|
|
||||||
make html
|
|
||||||
|
|
||||||
- name: debug
|
|
||||||
run: |
|
|
||||||
echo ">>>>> status"
|
|
||||||
git status
|
|
||||||
echo ">>>>> remotes"
|
|
||||||
git remote -v
|
|
||||||
echo ">>>>> branch"
|
|
||||||
git branch -v
|
|
||||||
echo ">>>>> log"
|
|
||||||
git log --pretty=oneline | head -5
|
|
||||||
|
|
||||||
- name: commit docs
|
|
||||||
run: |
|
|
||||||
git config user.name 'Github Build Bot'
|
|
||||||
git config user.email 'buildbot@fishtownanalytics.com'
|
|
||||||
git commit -am "Add generated CLI API docs"
|
|
||||||
git push -u origin ${{ github.head_ref }}
|
|
||||||
4
.github/workflows/jira-creation.yml
vendored
4
.github/workflows/jira-creation.yml
vendored
@@ -18,8 +18,8 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
call-label-action:
|
call-creation-action:
|
||||||
uses: dbt-labs/jira-actions/.github/workflows/jira-creation.yml@main
|
uses: dbt-labs/actions/.github/workflows/jira-creation-actions.yml@main
|
||||||
secrets:
|
secrets:
|
||||||
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
||||||
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
||||||
|
|||||||
2
.github/workflows/jira-label.yml
vendored
2
.github/workflows/jira-label.yml
vendored
@@ -19,7 +19,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
call-label-action:
|
call-label-action:
|
||||||
uses: dbt-labs/jira-actions/.github/workflows/jira-label.yml@main
|
uses: dbt-labs/actions/.github/workflows/jira-label-actions.yml@main
|
||||||
secrets:
|
secrets:
|
||||||
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
||||||
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
||||||
|
|||||||
4
.github/workflows/jira-transition.yml
vendored
4
.github/workflows/jira-transition.yml
vendored
@@ -19,8 +19,8 @@ on:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
call-label-action:
|
call-transition-action:
|
||||||
uses: dbt-labs/jira-actions/.github/workflows/jira-transition.yml@main
|
uses: dbt-labs/actions/.github/workflows/jira-transition-actions.yml@main
|
||||||
secrets:
|
secrets:
|
||||||
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }}
|
||||||
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }}
|
||||||
|
|||||||
67
.github/workflows/main.yml
vendored
67
.github/workflows/main.yml
vendored
@@ -42,10 +42,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4.3.0
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.8'
|
python-version: '3.8'
|
||||||
|
|
||||||
@@ -53,12 +53,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
python -m pip install --user --upgrade pip
|
python -m pip install --user --upgrade pip
|
||||||
python -m pip --version
|
python -m pip --version
|
||||||
python -m pip install pre-commit
|
make dev
|
||||||
pre-commit --version
|
|
||||||
python -m pip install mypy==0.942
|
|
||||||
mypy --version
|
mypy --version
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
python -m pip install -r dev-requirements.txt
|
|
||||||
dbt --version
|
dbt --version
|
||||||
|
|
||||||
- name: Run pre-commit hooks
|
- name: Run pre-commit hooks
|
||||||
@@ -73,18 +69,17 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
TOXENV: "unit"
|
TOXENV: "unit"
|
||||||
PYTEST_ADDOPTS: "-v --color=yes --csv unit_results.csv"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4.3.0
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
@@ -101,24 +96,26 @@ jobs:
|
|||||||
- name: Get current date
|
- name: Get current date
|
||||||
if: always()
|
if: always()
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y-%m-%dT%H_%M_%S')" #no colons allowed for artifacts
|
run: |
|
||||||
|
CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts
|
||||||
|
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- name: Upload Unit Test Coverage to Codecov
|
||||||
if: always()
|
if: ${{ matrix.python-version == '3.11' }}
|
||||||
with:
|
uses: codecov/codecov-action@v3
|
||||||
name: unit_results_${{ matrix.python-version }}-${{ steps.date.outputs.date }}.csv
|
env:
|
||||||
path: unit_results.csv
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
integration:
|
integration:
|
||||||
name: integration test / python ${{ matrix.python-version }} / ${{ matrix.os }}
|
name: integration test / python ${{ matrix.python-version }} / ${{ matrix.os }}
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 45
|
timeout-minutes: 60
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
python-version: ["3.8", "3.9", "3.10", "3.11"]
|
||||||
os: [ubuntu-20.04]
|
os: [ubuntu-20.04]
|
||||||
include:
|
include:
|
||||||
- python-version: 3.8
|
- python-version: 3.8
|
||||||
@@ -128,18 +125,22 @@ jobs:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
TOXENV: integration
|
TOXENV: integration
|
||||||
PYTEST_ADDOPTS: "-v --color=yes -n4 --csv integration_results.csv"
|
|
||||||
DBT_INVOCATION_ENV: github-actions
|
DBT_INVOCATION_ENV: github-actions
|
||||||
DBT_TEST_USER_1: dbt_test_user_1
|
DBT_TEST_USER_1: dbt_test_user_1
|
||||||
DBT_TEST_USER_2: dbt_test_user_2
|
DBT_TEST_USER_2: dbt_test_user_2
|
||||||
DBT_TEST_USER_3: dbt_test_user_3
|
DBT_TEST_USER_3: dbt_test_user_3
|
||||||
|
DD_CIVISIBILITY_AGENTLESS_ENABLED: true
|
||||||
|
DD_API_KEY: ${{ secrets.DATADOG_API_KEY }}
|
||||||
|
DD_SITE: datadoghq.com
|
||||||
|
DD_ENV: ci
|
||||||
|
DD_SERVICE: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python ${{ matrix.python-version }}
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
uses: actions/setup-python@v4.3.0
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
@@ -163,24 +164,26 @@ jobs:
|
|||||||
tox --version
|
tox --version
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: tox
|
run: tox -- --ddtrace
|
||||||
|
|
||||||
- name: Get current date
|
- name: Get current date
|
||||||
if: always()
|
if: always()
|
||||||
id: date
|
id: date
|
||||||
run: echo "::set-output name=date::$(date +'%Y_%m_%dT%H_%M_%S')" #no colons allowed for artifacts
|
run: |
|
||||||
|
CURRENT_DATE=$(date +'%Y-%m-%dT%H_%M_%S') # no colons allowed for artifacts
|
||||||
|
echo "date=$CURRENT_DATE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v3
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: logs_${{ matrix.python-version }}_${{ matrix.os }}_${{ steps.date.outputs.date }}
|
name: logs_${{ matrix.python-version }}_${{ matrix.os }}_${{ steps.date.outputs.date }}
|
||||||
path: ./logs
|
path: ./logs
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
- name: Upload Integration Test Coverage to Codecov
|
||||||
if: always()
|
if: ${{ matrix.python-version == '3.11' }}
|
||||||
with:
|
uses: codecov/codecov-action@v3
|
||||||
name: integration_results_${{ matrix.python-version }}_${{ matrix.os }}_${{ steps.date.outputs.date }}.csv
|
env:
|
||||||
path: integration_results.csv
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: build packages
|
name: build packages
|
||||||
@@ -189,10 +192,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4.3.0
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.8'
|
python-version: '3.8'
|
||||||
|
|
||||||
|
|||||||
265
.github/workflows/model_performance.yml
vendored
Normal file
265
.github/workflows/model_performance.yml
vendored
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
# **what?**
|
||||||
|
# This workflow models the performance characteristics of a point in time in dbt.
|
||||||
|
# It runs specific dbt commands on committed projects multiple times to create and
|
||||||
|
# commit information about the distribution to the current branch. For more information
|
||||||
|
# see the readme in the performance module at /performance/README.md.
|
||||||
|
#
|
||||||
|
# **why?**
|
||||||
|
# When developing new features, we can take quick performance samples and compare
|
||||||
|
# them against the commited baseline measurements produced by this workflow to detect
|
||||||
|
# some performance regressions at development time before they reach users.
|
||||||
|
#
|
||||||
|
# **when?**
|
||||||
|
# This is only run once directly after each release (for non-prereleases). If for some
|
||||||
|
# reason the results of a run are not satisfactory, it can also be triggered manually.
|
||||||
|
|
||||||
|
name: Model Performance Characteristics
|
||||||
|
|
||||||
|
on:
|
||||||
|
# runs after non-prereleases are published.
|
||||||
|
release:
|
||||||
|
types: [released]
|
||||||
|
# run manually from the actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
release_id:
|
||||||
|
description: 'dbt version to model (must be non-prerelease in Pypi)'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUNNER_CACHE_PATH: performance/runner/target/release/runner
|
||||||
|
|
||||||
|
# both jobs need to write
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
set-variables:
|
||||||
|
name: Setting Variables
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
cache_key: ${{ steps.variables.outputs.cache_key }}
|
||||||
|
release_id: ${{ steps.semver.outputs.base-version }}
|
||||||
|
release_branch: ${{ steps.variables.outputs.release_branch }}
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# explicitly checkout the performance runner from main regardless of which
|
||||||
|
# version we are modeling.
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Parse version into parts
|
||||||
|
id: semver
|
||||||
|
uses: dbt-labs/actions/parse-semver@v1
|
||||||
|
with:
|
||||||
|
version: ${{ github.event.inputs.release_id || github.event.release.tag_name }}
|
||||||
|
|
||||||
|
# collect all the variables that need to be used in subsequent jobs
|
||||||
|
- name: Set variables
|
||||||
|
id: variables
|
||||||
|
run: |
|
||||||
|
# create a cache key that will be used in the next job. without this the
|
||||||
|
# next job would have to checkout from main and hash the files itself.
|
||||||
|
echo "cache_key=${{ runner.os }}-${{ hashFiles('performance/runner/Cargo.toml')}}-${{ hashFiles('performance/runner/src/*') }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
branch_name="${{steps.semver.outputs.major}}.${{steps.semver.outputs.minor}}.latest"
|
||||||
|
echo "release_branch=$branch_name" >> $GITHUB_OUTPUT
|
||||||
|
echo "release branch is inferred to be ${branch_name}"
|
||||||
|
|
||||||
|
latest-runner:
|
||||||
|
name: Build or Fetch Runner
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [set-variables]
|
||||||
|
env:
|
||||||
|
RUSTFLAGS: "-D warnings"
|
||||||
|
steps:
|
||||||
|
- name: '[DEBUG] print variables'
|
||||||
|
run: |
|
||||||
|
echo "all variables defined in set-variables"
|
||||||
|
echo "cache_key: ${{ needs.set-variables.outputs.cache_key }}"
|
||||||
|
echo "release_id: ${{ needs.set-variables.outputs.release_id }}"
|
||||||
|
echo "release_branch: ${{ needs.set-variables.outputs.release_branch }}"
|
||||||
|
|
||||||
|
# explicitly checkout the performance runner from main regardless of which
|
||||||
|
# version we are modeling.
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
# attempts to access a previously cached runner
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
path: ${{ env.RUNNER_CACHE_PATH }}
|
||||||
|
key: ${{ needs.set-variables.outputs.cache_key }}
|
||||||
|
|
||||||
|
- name: Fetch Rust Toolchain
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Add fmt
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
run: rustup component add rustfmt
|
||||||
|
|
||||||
|
- name: Cargo fmt
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --manifest-path performance/runner/Cargo.toml --all -- --check
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --manifest-path performance/runner/Cargo.toml
|
||||||
|
|
||||||
|
- name: Build (optimized)
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
args: --release --manifest-path performance/runner/Cargo.toml
|
||||||
|
# the cache action automatically caches this binary at the end of the job
|
||||||
|
|
||||||
|
model:
|
||||||
|
# depends on `latest-runner` as a separate job so that failures in this job do not prevent
|
||||||
|
# a successfully tested and built binary from being cached.
|
||||||
|
needs: [set-variables, latest-runner]
|
||||||
|
name: Model a release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
|
||||||
|
- name: '[DEBUG] print variables'
|
||||||
|
run: |
|
||||||
|
echo "all variables defined in set-variables"
|
||||||
|
echo "cache_key: ${{ needs.set-variables.outputs.cache_key }}"
|
||||||
|
echo "release_id: ${{ needs.set-variables.outputs.release_id }}"
|
||||||
|
echo "release_branch: ${{ needs.set-variables.outputs.release_branch }}"
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.8"
|
||||||
|
|
||||||
|
- name: Install dbt
|
||||||
|
run: pip install dbt-postgres==${{ needs.set-variables.outputs.release_id }}
|
||||||
|
|
||||||
|
- name: Install Hyperfine
|
||||||
|
run: wget https://github.com/sharkdp/hyperfine/releases/download/v1.11.0/hyperfine_1.11.0_amd64.deb && sudo dpkg -i hyperfine_1.11.0_amd64.deb
|
||||||
|
|
||||||
|
# explicitly checkout main to get the latest project definitions
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
# this was built in the previous job so it will be there.
|
||||||
|
- name: Fetch Runner
|
||||||
|
uses: actions/cache@v3
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
path: ${{ env.RUNNER_CACHE_PATH }}
|
||||||
|
key: ${{ needs.set-variables.outputs.cache_key }}
|
||||||
|
|
||||||
|
- name: Move Runner
|
||||||
|
run: mv performance/runner/target/release/runner performance/app
|
||||||
|
|
||||||
|
- name: Change Runner Permissions
|
||||||
|
run: chmod +x ./performance/app
|
||||||
|
|
||||||
|
- name: '[DEBUG] ls baseline directory before run'
|
||||||
|
run: ls -R performance/baselines/
|
||||||
|
|
||||||
|
# `${{ github.workspace }}` is used to pass the absolute path
|
||||||
|
- name: Create directories
|
||||||
|
run: |
|
||||||
|
mkdir ${{ github.workspace }}/performance/tmp/
|
||||||
|
mkdir -p performance/baselines/${{ needs.set-variables.outputs.release_id }}/
|
||||||
|
|
||||||
|
# Run modeling with taking 20 samples
|
||||||
|
- name: Run Measurement
|
||||||
|
run: |
|
||||||
|
performance/app model -v ${{ needs.set-variables.outputs.release_id }} -b ${{ github.workspace }}/performance/baselines/ -p ${{ github.workspace }}/performance/projects/ -t ${{ github.workspace }}/performance/tmp/ -n 20
|
||||||
|
|
||||||
|
- name: '[DEBUG] ls baseline directory after run'
|
||||||
|
run: ls -R performance/baselines/
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: baseline
|
||||||
|
path: performance/baselines/${{ needs.set-variables.outputs.release_id }}/
|
||||||
|
|
||||||
|
create-pr:
|
||||||
|
name: Open PR for ${{ matrix.base-branch }}
|
||||||
|
|
||||||
|
# depends on `model` as a separate job so that the baseline can be committed to more than one branch
|
||||||
|
# i.e. release branch and main
|
||||||
|
needs: [set-variables, latest-runner, model]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- base-branch: refs/heads/main
|
||||||
|
target-branch: performance-bot/main_${{ needs.set-variables.outputs.release_id }}_${{GITHUB.RUN_ID}}
|
||||||
|
- base-branch: refs/heads/${{ needs.set-variables.outputs.release_branch }}
|
||||||
|
target-branch: performance-bot/release_${{ needs.set-variables.outputs.release_id }}_${{GITHUB.RUN_ID}}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: '[DEBUG] print variables'
|
||||||
|
run: |
|
||||||
|
echo "all variables defined in set-variables"
|
||||||
|
echo "cache_key: ${{ needs.set-variables.outputs.cache_key }}"
|
||||||
|
echo "release_id: ${{ needs.set-variables.outputs.release_id }}"
|
||||||
|
echo "release_branch: ${{ needs.set-variables.outputs.release_branch }}"
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ matrix.base-branch }}
|
||||||
|
|
||||||
|
- name: Create PR branch
|
||||||
|
run: |
|
||||||
|
git checkout -b ${{ matrix.target-branch }}
|
||||||
|
git push origin ${{ matrix.target-branch }}
|
||||||
|
git branch --set-upstream-to=origin/${{ matrix.target-branch }} ${{ matrix.target-branch }}
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: baseline
|
||||||
|
path: performance/baselines/${{ needs.set-variables.outputs.release_id }}
|
||||||
|
|
||||||
|
- name: '[DEBUG] ls baselines after artifact download'
|
||||||
|
run: ls -R performance/baselines/
|
||||||
|
|
||||||
|
- name: Commit baseline
|
||||||
|
uses: EndBug/add-and-commit@v9
|
||||||
|
with:
|
||||||
|
add: 'performance/baselines/*'
|
||||||
|
author_name: 'Github Build Bot'
|
||||||
|
author_email: 'buildbot@fishtownanalytics.com'
|
||||||
|
message: 'adding performance baseline for ${{ needs.set-variables.outputs.release_id }}'
|
||||||
|
push: 'origin origin/${{ matrix.target-branch }}'
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@v5
|
||||||
|
with:
|
||||||
|
author: 'Github Build Bot <buildbot@fishtownanalytics.com>'
|
||||||
|
base: ${{ matrix.base-branch }}
|
||||||
|
branch: '${{ matrix.target-branch }}'
|
||||||
|
title: 'Adding performance modeling for ${{needs.set-variables.outputs.release_id}} to ${{ matrix.base-branch }}'
|
||||||
|
body: 'Committing perf results for tracking for the ${{needs.set-variables.outputs.release_id}}'
|
||||||
|
labels: |
|
||||||
|
Skip Changelog
|
||||||
|
Performance
|
||||||
109
.github/workflows/nightly-release.yml
vendored
Normal file
109
.github/workflows/nightly-release.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# **what?**
|
||||||
|
# Nightly releases to GitHub and PyPI. This workflow produces the following outcome:
|
||||||
|
# - generate and validate data for night release (commit SHA, version number, release branch);
|
||||||
|
# - pass data to release workflow;
|
||||||
|
# - night release will be pushed to GitHub as a draft release;
|
||||||
|
# - night build will be pushed to test PyPI;
|
||||||
|
#
|
||||||
|
# **why?**
|
||||||
|
# Ensure an automated and tested release process for nightly builds
|
||||||
|
#
|
||||||
|
# **when?**
|
||||||
|
# This workflow runs on schedule or can be run manually on demand.
|
||||||
|
|
||||||
|
name: Nightly Test Release to GitHub and PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: # for manual triggering
|
||||||
|
schedule:
|
||||||
|
- cron: 0 9 * * *
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write # this is the permission that allows creating a new release
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
env:
|
||||||
|
RELEASE_BRANCH: "main"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
aggregate-release-data:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
commit_sha: ${{ steps.resolve-commit-sha.outputs.release_commit }}
|
||||||
|
version_number: ${{ steps.nightly-release-version.outputs.number }}
|
||||||
|
release_branch: ${{ steps.release-branch.outputs.name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout ${{ github.repository }} Branch ${{ env.RELEASE_BRANCH }}"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ env.RELEASE_BRANCH }}
|
||||||
|
|
||||||
|
- name: "Resolve Commit To Release"
|
||||||
|
id: resolve-commit-sha
|
||||||
|
run: |
|
||||||
|
commit_sha=$(git rev-parse HEAD)
|
||||||
|
echo "release_commit=$commit_sha" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: "Get Current Version Number"
|
||||||
|
id: version-number-sources
|
||||||
|
run: |
|
||||||
|
current_version=`awk -F"current_version = " '{print $2}' .bumpversion.cfg | tr '\n' ' '`
|
||||||
|
echo "current_version=$current_version" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: "Audit Version And Parse Into Parts"
|
||||||
|
id: semver
|
||||||
|
uses: dbt-labs/actions/parse-semver@v1.1.0
|
||||||
|
with:
|
||||||
|
version: ${{ steps.version-number-sources.outputs.current_version }}
|
||||||
|
|
||||||
|
- name: "Get Current Date"
|
||||||
|
id: current-date
|
||||||
|
run: echo "date=$(date +'%m%d%Y')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: "Generate Nightly Release Version Number"
|
||||||
|
id: nightly-release-version
|
||||||
|
run: |
|
||||||
|
number="${{ steps.semver.outputs.version }}.dev${{ steps.current-date.outputs.date }}"
|
||||||
|
echo "number=$number" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: "Audit Nightly Release Version And Parse Into Parts"
|
||||||
|
uses: dbt-labs/actions/parse-semver@v1.1.0
|
||||||
|
with:
|
||||||
|
version: ${{ steps.nightly-release-version.outputs.number }}
|
||||||
|
|
||||||
|
- name: "Set Release Branch"
|
||||||
|
id: release-branch
|
||||||
|
run: |
|
||||||
|
echo "name=${{ env.RELEASE_BRANCH }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
log-outputs-aggregate-release-data:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [aggregate-release-data]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "[DEBUG] Log Outputs"
|
||||||
|
run: |
|
||||||
|
echo commit_sha : ${{ needs.aggregate-release-data.outputs.commit_sha }}
|
||||||
|
echo version_number: ${{ needs.aggregate-release-data.outputs.version_number }}
|
||||||
|
echo release_branch: ${{ needs.aggregate-release-data.outputs.release_branch }}
|
||||||
|
|
||||||
|
release-github-pypi:
|
||||||
|
needs: [aggregate-release-data]
|
||||||
|
|
||||||
|
uses: ./.github/workflows/release.yml
|
||||||
|
with:
|
||||||
|
sha: ${{ needs.aggregate-release-data.outputs.commit_sha }}
|
||||||
|
target_branch: ${{ needs.aggregate-release-data.outputs.release_branch }}
|
||||||
|
version_number: ${{ needs.aggregate-release-data.outputs.version_number }}
|
||||||
|
build_script_path: "scripts/build-dist.sh"
|
||||||
|
env_setup_script_path: "scripts/env-setup.sh"
|
||||||
|
s3_bucket_name: "core-team-artifacts"
|
||||||
|
package_test_command: "dbt --version"
|
||||||
|
test_run: true
|
||||||
|
nightly_release: true
|
||||||
|
secrets: inherit
|
||||||
43
.github/workflows/release-branch-tests.yml
vendored
43
.github/workflows/release-branch-tests.yml
vendored
@@ -1,11 +1,7 @@
|
|||||||
# **what?**
|
# **what?**
|
||||||
# The purpose of this workflow is to trigger CI to run for each
|
# The purpose of this workflow is to trigger CI to run for each
|
||||||
# release branch and main branch on a regular cadence. If the CI workflow
|
# release branch and main branch on a regular cadence. If the CI workflow
|
||||||
# fails for a branch, it will post to dev-core-alerts to raise awareness.
|
# fails for a branch, it will post to #dev-core-alerts to raise awareness.
|
||||||
# The 'aurelien-baudet/workflow-dispatch' Action triggers the existing
|
|
||||||
# CI worklow file on the given branch to run so that even if we change the
|
|
||||||
# CI workflow file in the future, the one that is tailored for the given
|
|
||||||
# release branch will be used.
|
|
||||||
|
|
||||||
# **why?**
|
# **why?**
|
||||||
# Ensures release branches and main are always shippable and not broken.
|
# Ensures release branches and main are always shippable and not broken.
|
||||||
@@ -28,35 +24,8 @@ on:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
kick-off-ci:
|
run_tests:
|
||||||
name: Kick-off CI
|
uses: dbt-labs/actions/.github/workflows/release-branch-tests.yml@main
|
||||||
runs-on: ubuntu-latest
|
with:
|
||||||
|
workflows_to_run: '["main.yml"]'
|
||||||
strategy:
|
secrets: inherit
|
||||||
# must run CI 1 branch at a time b/c the workflow-dispatch Action polls for
|
|
||||||
# latest run for results and it gets confused when we kick off multiple runs
|
|
||||||
# at once. There is a race condition so we will just run in sequential order.
|
|
||||||
max-parallel: 1
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
branch: [1.0.latest, 1.1.latest, 1.2.latest, 1.3.latest, main]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Call CI workflow for ${{ matrix.branch }} branch
|
|
||||||
id: trigger-step
|
|
||||||
uses: aurelien-baudet/workflow-dispatch@v2.1.1
|
|
||||||
with:
|
|
||||||
workflow: main.yml
|
|
||||||
ref: ${{ matrix.branch }}
|
|
||||||
token: ${{ secrets.FISHTOWN_BOT_PAT }}
|
|
||||||
|
|
||||||
- name: Post failure to Slack
|
|
||||||
uses: ravsamhq/notify-slack-action@v1
|
|
||||||
if: ${{ always() && !contains(steps.trigger-step.outputs.workflow-conclusion,'success') }}
|
|
||||||
with:
|
|
||||||
status: ${{ job.status }}
|
|
||||||
notification_title: 'dbt-core scheduled run of "${{ matrix.branch }}" branch not successful'
|
|
||||||
message_format: ':x: CI on branch "${{ matrix.branch }}" ${{ steps.trigger-step.outputs.workflow-conclusion }}'
|
|
||||||
footer: 'Linked failed CI run ${{ steps.trigger-step.outputs.workflow-url }}'
|
|
||||||
env:
|
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DEV_CORE_ALERTS }}
|
|
||||||
|
|||||||
24
.github/workflows/release-docker.yml
vendored
24
.github/workflows/release-docker.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
|||||||
latest: ${{ steps.latest.outputs.latest }}
|
latest: ${{ steps.latest.outputs.latest }}
|
||||||
minor_latest: ${{ steps.latest.outputs.minor_latest }}
|
minor_latest: ${{ steps.latest.outputs.minor_latest }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v3
|
||||||
- name: Split version
|
- name: Split version
|
||||||
id: version
|
id: version
|
||||||
run: |
|
run: |
|
||||||
IFS="." read -r MAJOR MINOR PATCH <<< ${{ github.event.inputs.version_number }}
|
IFS="." read -r MAJOR MINOR PATCH <<< ${{ github.event.inputs.version_number }}
|
||||||
echo "::set-output name=major::$MAJOR"
|
echo "major=$MAJOR" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=minor::$MINOR"
|
echo "minor=$MINOR" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=patch::$PATCH"
|
echo "patch=$PATCH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Is pkg 'latest'
|
- name: Is pkg 'latest'
|
||||||
id: latest
|
id: latest
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
needs: [get_version_meta]
|
needs: [get_version_meta]
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
build_and_push:
|
build_and_push:
|
||||||
name: Build images and push to GHCR
|
name: Build images and push to GHCR
|
||||||
@@ -70,18 +70,20 @@ jobs:
|
|||||||
- name: Get docker build arg
|
- name: Get docker build arg
|
||||||
id: build_arg
|
id: build_arg
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=build_arg_name::"$(echo ${{ github.event.inputs.package }} | sed 's/\-/_/g')
|
BUILD_ARG_NAME=$(echo ${{ github.event.inputs.package }} | sed 's/\-/_/g')
|
||||||
echo "::set-output name=build_arg_value::"$(echo ${{ github.event.inputs.package }} | sed 's/postgres/core/g')
|
BUILD_ARG_VALUE=$(echo ${{ github.event.inputs.package }} | sed 's/postgres/core/g')
|
||||||
|
echo "build_arg_name=$BUILD_ARG_NAME" >> $GITHUB_OUTPUT
|
||||||
|
echo "build_arg_value=$BUILD_ARG_VALUE" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Log in to the GHCR
|
- name: Log in to the GHCR
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push MAJOR.MINOR.PATCH tag
|
- name: Build and push MAJOR.MINOR.PATCH tag
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
push: True
|
push: True
|
||||||
@@ -92,7 +94,7 @@ jobs:
|
|||||||
ghcr.io/dbt-labs/${{ github.event.inputs.package }}:${{ github.event.inputs.version_number }}
|
ghcr.io/dbt-labs/${{ github.event.inputs.package }}:${{ github.event.inputs.version_number }}
|
||||||
|
|
||||||
- name: Build and push MINOR.latest tag
|
- name: Build and push MINOR.latest tag
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v4
|
||||||
if: ${{ needs.get_version_meta.outputs.minor_latest == 'True' }}
|
if: ${{ needs.get_version_meta.outputs.minor_latest == 'True' }}
|
||||||
with:
|
with:
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
@@ -104,7 +106,7 @@ jobs:
|
|||||||
ghcr.io/dbt-labs/${{ github.event.inputs.package }}:${{ needs.get_version_meta.outputs.major }}.${{ needs.get_version_meta.outputs.minor }}.latest
|
ghcr.io/dbt-labs/${{ github.event.inputs.package }}:${{ needs.get_version_meta.outputs.major }}.${{ needs.get_version_meta.outputs.minor }}.latest
|
||||||
|
|
||||||
- name: Build and push latest tag
|
- name: Build and push latest tag
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v4
|
||||||
if: ${{ needs.get_version_meta.outputs.latest == 'True' }}
|
if: ${{ needs.get_version_meta.outputs.latest == 'True' }}
|
||||||
with:
|
with:
|
||||||
file: docker/Dockerfile
|
file: docker/Dockerfile
|
||||||
|
|||||||
339
.github/workflows/release.yml
vendored
339
.github/workflows/release.yml
vendored
@@ -1,24 +1,110 @@
|
|||||||
# **what?**
|
# **what?**
|
||||||
# Take the given commit, run unit tests specifically on that sha, build and
|
# Release workflow provides the following steps:
|
||||||
# package it, and then release to GitHub and PyPi with that specific build
|
# - checkout the given commit;
|
||||||
|
# - validate version in sources and changelog file for given version;
|
||||||
|
# - bump the version and generate a changelog if needed;
|
||||||
|
# - merge all changes to the target branch if needed;
|
||||||
|
# - run unit and integration tests against given commit;
|
||||||
|
# - build and package that SHA;
|
||||||
|
# - release it to GitHub and PyPI with that specific build;
|
||||||
|
#
|
||||||
# **why?**
|
# **why?**
|
||||||
# Ensure an automated and tested release process
|
# Ensure an automated and tested release process
|
||||||
|
#
|
||||||
# **when?**
|
# **when?**
|
||||||
# This will only run manually with a given sha and version
|
# This workflow can be run manually on demand or can be called by other workflows
|
||||||
|
|
||||||
name: Release to GitHub and PyPi
|
name: Release to GitHub and PyPI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
sha:
|
sha:
|
||||||
description: 'The last commit sha in the release'
|
description: "The last commit sha in the release"
|
||||||
required: true
|
type: string
|
||||||
|
required: true
|
||||||
|
target_branch:
|
||||||
|
description: "The branch to release from"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
version_number:
|
version_number:
|
||||||
description: 'The release version number (i.e. 1.0.0b1)'
|
description: "The release version number (i.e. 1.0.0b1)"
|
||||||
required: true
|
type: string
|
||||||
|
required: true
|
||||||
|
build_script_path:
|
||||||
|
description: "Build script path"
|
||||||
|
type: string
|
||||||
|
default: "scripts/build-dist.sh"
|
||||||
|
required: true
|
||||||
|
env_setup_script_path:
|
||||||
|
description: "Environment setup script path"
|
||||||
|
type: string
|
||||||
|
default: "scripts/env-setup.sh"
|
||||||
|
required: false
|
||||||
|
s3_bucket_name:
|
||||||
|
description: "AWS S3 bucket name"
|
||||||
|
type: string
|
||||||
|
default: "core-team-artifacts"
|
||||||
|
required: true
|
||||||
|
package_test_command:
|
||||||
|
description: "Package test command"
|
||||||
|
type: string
|
||||||
|
default: "dbt --version"
|
||||||
|
required: true
|
||||||
|
test_run:
|
||||||
|
description: "Test run (Publish release as draft)"
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
required: false
|
||||||
|
nightly_release:
|
||||||
|
description: "Nightly release to dev environment"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
required: false
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
sha:
|
||||||
|
description: "The last commit sha in the release"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
target_branch:
|
||||||
|
description: "The branch to release from"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
version_number:
|
||||||
|
description: "The release version number (i.e. 1.0.0b1)"
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
build_script_path:
|
||||||
|
description: "Build script path"
|
||||||
|
type: string
|
||||||
|
default: "scripts/build-dist.sh"
|
||||||
|
required: true
|
||||||
|
env_setup_script_path:
|
||||||
|
description: "Environment setup script path"
|
||||||
|
type: string
|
||||||
|
default: "scripts/env-setup.sh"
|
||||||
|
required: false
|
||||||
|
s3_bucket_name:
|
||||||
|
description: "AWS S3 bucket name"
|
||||||
|
type: string
|
||||||
|
default: "core-team-artifacts"
|
||||||
|
required: true
|
||||||
|
package_test_command:
|
||||||
|
description: "Package test command"
|
||||||
|
type: string
|
||||||
|
default: "dbt --version"
|
||||||
|
required: true
|
||||||
|
test_run:
|
||||||
|
description: "Test run (Publish release as draft)"
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
required: false
|
||||||
|
nightly_release:
|
||||||
|
description: "Nightly release to dev environment"
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
required: false
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write # this is the permission that allows creating a new release
|
contents: write # this is the permission that allows creating a new release
|
||||||
@@ -28,175 +114,116 @@ defaults:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
unit:
|
log-inputs:
|
||||||
name: Unit test
|
name: Log Inputs
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
|
||||||
TOXENV: "unit"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: "[DEBUG] Print Variables"
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
ref: ${{ github.event.inputs.sha }}
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
|
|
||||||
- name: Install python dependencies
|
|
||||||
run: |
|
run: |
|
||||||
pip install --user --upgrade pip
|
echo The last commit sha in the release: ${{ inputs.sha }}
|
||||||
pip install tox
|
echo The branch to release from: ${{ inputs.target_branch }}
|
||||||
pip --version
|
echo The release version number: ${{ inputs.version_number }}
|
||||||
tox --version
|
echo Build script path: ${{ inputs.build_script_path }}
|
||||||
|
echo Environment setup script path: ${{ inputs.env_setup_script_path }}
|
||||||
|
echo AWS S3 bucket name: ${{ inputs.s3_bucket_name }}
|
||||||
|
echo Package test command: ${{ inputs.package_test_command }}
|
||||||
|
echo Test run: ${{ inputs.test_run }}
|
||||||
|
echo Nightly release: ${{ inputs.nightly_release }}
|
||||||
|
|
||||||
- name: Run tox
|
bump-version-generate-changelog:
|
||||||
run: tox
|
name: Bump package version, Generate changelog
|
||||||
|
|
||||||
build:
|
uses: dbt-labs/dbt-release/.github/workflows/release-prep.yml@main
|
||||||
name: build packages
|
|
||||||
|
with:
|
||||||
|
sha: ${{ inputs.sha }}
|
||||||
|
version_number: ${{ inputs.version_number }}
|
||||||
|
target_branch: ${{ inputs.target_branch }}
|
||||||
|
env_setup_script_path: ${{ inputs.env_setup_script_path }}
|
||||||
|
test_run: ${{ inputs.test_run }}
|
||||||
|
nightly_release: ${{ inputs.nightly_release }}
|
||||||
|
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
log-outputs-bump-version-generate-changelog:
|
||||||
|
name: "[Log output] Bump package version, Generate changelog"
|
||||||
|
if: ${{ !failure() && !cancelled() }}
|
||||||
|
|
||||||
|
needs: [bump-version-generate-changelog]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Print variables
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
persist-credentials: false
|
|
||||||
ref: ${{ github.event.inputs.sha }}
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
|
|
||||||
- name: Install python dependencies
|
|
||||||
run: |
|
run: |
|
||||||
pip install --user --upgrade pip
|
echo Final SHA : ${{ needs.bump-version-generate-changelog.outputs.final_sha }}
|
||||||
pip install --upgrade setuptools wheel twine check-wheel-contents
|
echo Changelog path: ${{ needs.bump-version-generate-changelog.outputs.changelog_path }}
|
||||||
pip --version
|
|
||||||
|
|
||||||
- name: Build distributions
|
build-test-package:
|
||||||
run: ./scripts/build-dist.sh
|
name: Build, Test, Package
|
||||||
|
if: ${{ !failure() && !cancelled() }}
|
||||||
|
needs: [bump-version-generate-changelog]
|
||||||
|
|
||||||
- name: Show distributions
|
uses: dbt-labs/dbt-release/.github/workflows/build.yml@main
|
||||||
run: ls -lh dist/
|
|
||||||
|
|
||||||
- name: Check distribution descriptions
|
with:
|
||||||
run: |
|
sha: ${{ needs.bump-version-generate-changelog.outputs.final_sha }}
|
||||||
twine check dist/*
|
version_number: ${{ inputs.version_number }}
|
||||||
|
changelog_path: ${{ needs.bump-version-generate-changelog.outputs.changelog_path }}
|
||||||
|
build_script_path: ${{ inputs.build_script_path }}
|
||||||
|
s3_bucket_name: ${{ inputs.s3_bucket_name }}
|
||||||
|
package_test_command: ${{ inputs.package_test_command }}
|
||||||
|
test_run: ${{ inputs.test_run }}
|
||||||
|
nightly_release: ${{ inputs.nightly_release }}
|
||||||
|
|
||||||
- name: Check wheel contents
|
secrets:
|
||||||
run: |
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
check-wheel-contents dist/*.whl --ignore W007,W008
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: |
|
|
||||||
dist/
|
|
||||||
!dist/dbt-${{github.event.inputs.version_number}}.tar.gz
|
|
||||||
|
|
||||||
test-build:
|
|
||||||
name: verify packages
|
|
||||||
|
|
||||||
needs: [build, unit]
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: 3.8
|
|
||||||
|
|
||||||
- name: Install python dependencies
|
|
||||||
run: |
|
|
||||||
pip install --user --upgrade pip
|
|
||||||
pip install --upgrade wheel
|
|
||||||
pip --version
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v2
|
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: dist/
|
|
||||||
|
|
||||||
- name: Show distributions
|
|
||||||
run: ls -lh dist/
|
|
||||||
|
|
||||||
- name: Install wheel distributions
|
|
||||||
run: |
|
|
||||||
find ./dist/*.whl -maxdepth 1 -type f | xargs pip install --force-reinstall --find-links=dist/
|
|
||||||
|
|
||||||
- name: Check wheel distributions
|
|
||||||
run: |
|
|
||||||
dbt --version
|
|
||||||
|
|
||||||
- name: Install source distributions
|
|
||||||
run: |
|
|
||||||
find ./dist/*.gz -maxdepth 1 -type f | xargs pip install --force-reinstall --find-links=dist/
|
|
||||||
|
|
||||||
- name: Check source distributions
|
|
||||||
run: |
|
|
||||||
dbt --version
|
|
||||||
|
|
||||||
github-release:
|
github-release:
|
||||||
name: GitHub Release
|
name: GitHub Release
|
||||||
|
if: ${{ !failure() && !cancelled() }}
|
||||||
|
|
||||||
needs: test-build
|
needs: [bump-version-generate-changelog, build-test-package]
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
uses: dbt-labs/dbt-release/.github/workflows/github-release.yml@main
|
||||||
|
|
||||||
steps:
|
with:
|
||||||
- uses: actions/download-artifact@v2
|
sha: ${{ needs.bump-version-generate-changelog.outputs.final_sha }}
|
||||||
with:
|
version_number: ${{ inputs.version_number }}
|
||||||
name: dist
|
changelog_path: ${{ needs.bump-version-generate-changelog.outputs.changelog_path }}
|
||||||
path: '.'
|
test_run: ${{ inputs.test_run }}
|
||||||
|
|
||||||
# Need to set an output variable because env variables can't be taken as input
|
|
||||||
# This is needed for the next step with releasing to GitHub
|
|
||||||
- name: Find release type
|
|
||||||
id: release_type
|
|
||||||
env:
|
|
||||||
IS_PRERELEASE: ${{ contains(github.event.inputs.version_number, 'rc') || contains(github.event.inputs.version_number, 'b') }}
|
|
||||||
run: |
|
|
||||||
echo ::set-output name=isPrerelease::$IS_PRERELEASE
|
|
||||||
|
|
||||||
- name: Creating GitHub Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
name: dbt-core v${{github.event.inputs.version_number}}
|
|
||||||
tag_name: v${{github.event.inputs.version_number}}
|
|
||||||
prerelease: ${{ steps.release_type.outputs.isPrerelease }}
|
|
||||||
target_commitish: ${{github.event.inputs.sha}}
|
|
||||||
body: |
|
|
||||||
[Release notes](https://github.com/dbt-labs/dbt-core/blob/main/CHANGELOG.md)
|
|
||||||
files: |
|
|
||||||
dbt_postgres-${{github.event.inputs.version_number}}-py3-none-any.whl
|
|
||||||
dbt_core-${{github.event.inputs.version_number}}-py3-none-any.whl
|
|
||||||
dbt-postgres-${{github.event.inputs.version_number}}.tar.gz
|
|
||||||
dbt-core-${{github.event.inputs.version_number}}.tar.gz
|
|
||||||
|
|
||||||
pypi-release:
|
pypi-release:
|
||||||
name: Pypi release
|
name: PyPI Release
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
needs: [github-release]
|
||||||
|
|
||||||
needs: github-release
|
uses: dbt-labs/dbt-release/.github/workflows/pypi-release.yml@main
|
||||||
|
|
||||||
environment: PypiProd
|
with:
|
||||||
steps:
|
version_number: ${{ inputs.version_number }}
|
||||||
- uses: actions/download-artifact@v2
|
test_run: ${{ inputs.test_run }}
|
||||||
with:
|
|
||||||
name: dist
|
|
||||||
path: 'dist'
|
|
||||||
|
|
||||||
- name: Publish distribution to PyPI
|
secrets:
|
||||||
uses: pypa/gh-action-pypi-publish@v1.4.2
|
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
with:
|
TEST_PYPI_API_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
||||||
|
slack-notification:
|
||||||
|
name: Slack Notification
|
||||||
|
if: ${{ failure() && (!inputs.test_run || inputs.nightly_release) }}
|
||||||
|
|
||||||
|
needs:
|
||||||
|
[
|
||||||
|
bump-version-generate-changelog,
|
||||||
|
build-test-package,
|
||||||
|
github-release,
|
||||||
|
pypi-release,
|
||||||
|
]
|
||||||
|
|
||||||
|
uses: dbt-labs/dbt-release/.github/workflows/slack-post-notification.yml@main
|
||||||
|
with:
|
||||||
|
status: "failure"
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DEV_CORE_ALERTS }}
|
||||||
|
|||||||
8
.github/workflows/schema-check.yml
vendored
8
.github/workflows/schema-check.yml
vendored
@@ -37,17 +37,17 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
|
|
||||||
- name: Checkout dbt repo
|
- name: Checkout dbt repo
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ env.DBT_REPO_DIRECTORY }}
|
path: ${{ env.DBT_REPO_DIRECTORY }}
|
||||||
|
|
||||||
- name: Checkout schemas.getdbt.com repo
|
- name: Checkout schemas.getdbt.com repo
|
||||||
uses: actions/checkout@v2.3.4
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: dbt-labs/schemas.getdbt.com
|
repository: dbt-labs/schemas.getdbt.com
|
||||||
ref: 'main'
|
ref: 'main'
|
||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Upload schema diff
|
- name: Upload schema diff
|
||||||
uses: actions/upload-artifact@v2.2.4
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
with:
|
with:
|
||||||
name: 'schema_schanges.txt'
|
name: 'schema_schanges.txt'
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ jobs:
|
|||||||
LOG_DIR: "/home/runner/work/dbt-core/dbt-core/logs"
|
LOG_DIR: "/home/runner/work/dbt-core/dbt-core/logs"
|
||||||
# tells integration tests to output into json format
|
# tells integration tests to output into json format
|
||||||
DBT_LOG_FORMAT: "json"
|
DBT_LOG_FORMAT: "json"
|
||||||
|
# tell eventmgr to convert logging events into bytes
|
||||||
|
DBT_TEST_BINARY_SERIALIZATION: "true"
|
||||||
# Additional test users
|
# Additional test users
|
||||||
DBT_TEST_USER_1: dbt_test_user_1
|
DBT_TEST_USER_1: dbt_test_user_1
|
||||||
DBT_TEST_USER_2: dbt_test_user_2
|
DBT_TEST_USER_2: dbt_test_user_2
|
||||||
@@ -37,12 +39,12 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: checkout dev
|
- name: checkout dev
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2.2.2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.8"
|
python-version: "3.8"
|
||||||
|
|
||||||
|
|||||||
155
.github/workflows/test-repeater.yml
vendored
Normal file
155
.github/workflows/test-repeater.yml
vendored
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# **what?**
|
||||||
|
# This workflow will test all test(s) at the input path given number of times to determine if it's flaky or not. You can test with any supported OS/Python combination.
|
||||||
|
# This is batched in 10 to allow more test iterations faster.
|
||||||
|
|
||||||
|
# **why?**
|
||||||
|
# Testing if a test is flaky and if a previously flaky test has been fixed. This allows easy testing on supported python versions and OS combinations.
|
||||||
|
|
||||||
|
# **when?**
|
||||||
|
# This is triggered manually from dbt-core.
|
||||||
|
|
||||||
|
name: Flaky Tester
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branch:
|
||||||
|
description: 'Branch to check out'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
default: 'main'
|
||||||
|
test_path:
|
||||||
|
description: 'Path to single test to run (ex: tests/functional/retry/test_retry.py::TestRetry::test_fail_fast)'
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
default: 'tests/functional/...'
|
||||||
|
python_version:
|
||||||
|
description: 'Version of Python to Test Against'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- '3.8'
|
||||||
|
- '3.9'
|
||||||
|
- '3.10'
|
||||||
|
- '3.11'
|
||||||
|
os:
|
||||||
|
description: 'OS to run test in'
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
num_runs_per_batch:
|
||||||
|
description: 'Max number of times to run the test per batch. We always run 10 batches.'
|
||||||
|
type: number
|
||||||
|
required: true
|
||||||
|
default: '50'
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
debug:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "[DEBUG] Output Inputs"
|
||||||
|
run: |
|
||||||
|
echo "Branch: ${{ inputs.branch }}"
|
||||||
|
echo "test_path: ${{ inputs.test_path }}"
|
||||||
|
echo "python_version: ${{ inputs.python_version }}"
|
||||||
|
echo "os: ${{ inputs.os }}"
|
||||||
|
echo "num_runs_per_batch: ${{ inputs.num_runs_per_batch }}"
|
||||||
|
|
||||||
|
pytest:
|
||||||
|
runs-on: ${{ inputs.os }}
|
||||||
|
strategy:
|
||||||
|
# run all batches, even if one fails. This informs how flaky the test may be.
|
||||||
|
fail-fast: false
|
||||||
|
# using a matrix to speed up the jobs since the matrix will run in parallel when runners are available
|
||||||
|
matrix:
|
||||||
|
batch: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
|
||||||
|
env:
|
||||||
|
PYTEST_ADDOPTS: "-v --color=yes -n4 --csv integration_results.csv"
|
||||||
|
DBT_TEST_USER_1: dbt_test_user_1
|
||||||
|
DBT_TEST_USER_2: dbt_test_user_2
|
||||||
|
DBT_TEST_USER_3: dbt_test_user_3
|
||||||
|
DD_CIVISIBILITY_AGENTLESS_ENABLED: true
|
||||||
|
DD_API_KEY: ${{ secrets.DATADOG_API_KEY }}
|
||||||
|
DD_SITE: datadoghq.com
|
||||||
|
DD_ENV: ci
|
||||||
|
DD_SERVICE: ${{ github.event.repository.name }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.branch }}
|
||||||
|
|
||||||
|
- name: "Setup Python"
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "${{ inputs.python_version }}"
|
||||||
|
|
||||||
|
- name: "Setup Dev Environment"
|
||||||
|
run: make dev
|
||||||
|
|
||||||
|
- name: "Set up postgres (linux)"
|
||||||
|
if: inputs.os == 'ubuntu-latest'
|
||||||
|
run: make setup-db
|
||||||
|
|
||||||
|
# mac and windows don't use make due to limitations with docker with those runners in GitHub
|
||||||
|
- name: "Set up postgres (macos)"
|
||||||
|
if: inputs.os == 'macos-latest'
|
||||||
|
uses: ./.github/actions/setup-postgres-macos
|
||||||
|
|
||||||
|
- name: "Set up postgres (windows)"
|
||||||
|
if: inputs.os == 'windows-latest'
|
||||||
|
uses: ./.github/actions/setup-postgres-windows
|
||||||
|
|
||||||
|
- name: "Test Command"
|
||||||
|
id: command
|
||||||
|
run: |
|
||||||
|
test_command="python -m pytest ${{ inputs.test_path }}"
|
||||||
|
echo "test_command=$test_command" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: "Run test ${{ inputs.num_runs_per_batch }} times"
|
||||||
|
id: pytest
|
||||||
|
run: |
|
||||||
|
set +e
|
||||||
|
for ((i=1; i<=${{ inputs.num_runs_per_batch }}; i++))
|
||||||
|
do
|
||||||
|
echo "Running pytest iteration $i..."
|
||||||
|
python -m pytest --ddtrace ${{ inputs.test_path }}
|
||||||
|
exit_code=$?
|
||||||
|
|
||||||
|
if [[ $exit_code -eq 0 ]]; then
|
||||||
|
success=$((success + 1))
|
||||||
|
echo "Iteration $i: Success"
|
||||||
|
else
|
||||||
|
failure=$((failure + 1))
|
||||||
|
echo "Iteration $i: Failure"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==========================="
|
||||||
|
echo "Successful runs: $success"
|
||||||
|
echo "Failed runs: $failure"
|
||||||
|
echo "==========================="
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "failure=$failure" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: "Success and Failure Summary: ${{ inputs.os }}/Python ${{ inputs.python_version }}"
|
||||||
|
run: |
|
||||||
|
echo "Batch: ${{ matrix.batch }}"
|
||||||
|
echo "Successful runs: ${{ steps.pytest.outputs.success }}"
|
||||||
|
echo "Failed runs: ${{ steps.pytest.outputs.failure }}"
|
||||||
|
|
||||||
|
- name: "Error for Failures"
|
||||||
|
if: ${{ steps.pytest.outputs.failure }}
|
||||||
|
run: |
|
||||||
|
echo "Batch ${{ matrix.batch }} failed ${{ steps.pytest.outputs.failure }} of ${{ inputs.num_runs_per_batch }} tests"
|
||||||
|
exit 1
|
||||||
12
.github/workflows/triage-labels.yml
vendored
12
.github/workflows/triage-labels.yml
vendored
@@ -24,10 +24,8 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
triage_label:
|
triage_label:
|
||||||
if: contains(github.event.issue.labels.*.name, 'awaiting_response')
|
if: contains(github.event.issue.labels.*.name, 'awaiting_response')
|
||||||
runs-on: ubuntu-latest
|
uses: dbt-labs/actions/.github/workflows/swap-labels.yml@main
|
||||||
steps:
|
with:
|
||||||
- name: initial labeling
|
add_label: "triage"
|
||||||
uses: andymckay/labeler@master
|
remove_label: "awaiting_response"
|
||||||
with:
|
secrets: inherit
|
||||||
add-labels: "triage"
|
|
||||||
remove-labels: "awaiting_response"
|
|
||||||
|
|||||||
107
.github/workflows/version-bump.yml
vendored
107
.github/workflows/version-bump.yml
vendored
@@ -20,106 +20,9 @@ on:
|
|||||||
description: 'The version number to bump to (ex. 1.2.0, 1.3.0b1)'
|
description: 'The version number to bump to (ex. 1.2.0, 1.3.0b1)'
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump:
|
version_bump_and_changie:
|
||||||
runs-on: ubuntu-latest
|
uses: dbt-labs/actions/.github/workflows/version-bump.yml@main
|
||||||
steps:
|
with:
|
||||||
- name: "[DEBUG] Print Variables"
|
version_number: ${{ inputs.version_number }}
|
||||||
run: |
|
secrets: inherit # ok since what we are calling is internally maintained
|
||||||
echo "all variables defined as inputs"
|
|
||||||
echo The version_number: ${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Check out the repository
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: "3.8"
|
|
||||||
|
|
||||||
- name: Install python dependencies
|
|
||||||
run: |
|
|
||||||
python3 -m venv env
|
|
||||||
source env/bin/activate
|
|
||||||
pip install --upgrade pip
|
|
||||||
|
|
||||||
- name: Add Homebrew to PATH
|
|
||||||
run: |
|
|
||||||
echo "/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" >> $GITHUB_PATH
|
|
||||||
|
|
||||||
- name: Install Homebrew packages
|
|
||||||
run: |
|
|
||||||
brew install pre-commit
|
|
||||||
brew tap miniscruff/changie https://github.com/miniscruff/changie
|
|
||||||
brew install changie
|
|
||||||
|
|
||||||
- name: Audit Version and Parse Into Parts
|
|
||||||
id: semver
|
|
||||||
uses: dbt-labs/actions/parse-semver@v1
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Set branch value
|
|
||||||
id: variables
|
|
||||||
run: |
|
|
||||||
echo "::set-output name=BRANCH_NAME::prep-release/${{ github.event.inputs.version_number }}_$GITHUB_RUN_ID"
|
|
||||||
|
|
||||||
- name: Create PR branch
|
|
||||||
run: |
|
|
||||||
git checkout -b ${{ steps.variables.outputs.BRANCH_NAME }}
|
|
||||||
git push origin ${{ steps.variables.outputs.BRANCH_NAME }}
|
|
||||||
git branch --set-upstream-to=origin/${{ steps.variables.outputs.BRANCH_NAME }} ${{ steps.variables.outputs.BRANCH_NAME }}
|
|
||||||
|
|
||||||
- name: Bump version
|
|
||||||
run: |
|
|
||||||
source env/bin/activate
|
|
||||||
pip install -r dev-requirements.txt
|
|
||||||
env/bin/bumpversion --allow-dirty --new-version ${{ github.event.inputs.version_number }} major
|
|
||||||
git status
|
|
||||||
|
|
||||||
- name: Run changie
|
|
||||||
run: |
|
|
||||||
if [[ ${{ steps.semver.outputs.is-pre-release }} -eq 1 ]]
|
|
||||||
then
|
|
||||||
changie batch ${{ steps.semver.outputs.base-version }} --move-dir '${{ steps.semver.outputs.base-version }}' --prerelease '${{ steps.semver.outputs.pre-release }}'
|
|
||||||
else
|
|
||||||
changie batch ${{ steps.semver.outputs.base-version }} --include '${{ steps.semver.outputs.base-version }}' --remove-prereleases
|
|
||||||
fi
|
|
||||||
changie merge
|
|
||||||
git status
|
|
||||||
|
|
||||||
# this step will fail on whitespace errors but also correct them
|
|
||||||
- name: Remove trailing whitespace
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
pre-commit run trailing-whitespace --files .bumpversion.cfg CHANGELOG.md .changes/*
|
|
||||||
git status
|
|
||||||
|
|
||||||
# this step will fail on newline errors but also correct them
|
|
||||||
- name: Removing extra newlines
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
pre-commit run end-of-file-fixer --files .bumpversion.cfg CHANGELOG.md .changes/*
|
|
||||||
git status
|
|
||||||
|
|
||||||
- name: Commit version bump to branch
|
|
||||||
uses: EndBug/add-and-commit@v7
|
|
||||||
with:
|
|
||||||
author_name: 'Github Build Bot'
|
|
||||||
author_email: 'buildbot@fishtownanalytics.com'
|
|
||||||
message: 'Bumping version to ${{ github.event.inputs.version_number }} and generate CHANGELOG'
|
|
||||||
branch: '${{ steps.variables.outputs.BRANCH_NAME }}'
|
|
||||||
push: 'origin origin/${{ steps.variables.outputs.BRANCH_NAME }}'
|
|
||||||
|
|
||||||
- name: Create Pull Request
|
|
||||||
uses: peter-evans/create-pull-request@v3
|
|
||||||
with:
|
|
||||||
author: 'Github Build Bot <buildbot@fishtownanalytics.com>'
|
|
||||||
base: ${{github.ref}}
|
|
||||||
title: 'Bumping version to ${{ github.event.inputs.version_number }} and generate changelog'
|
|
||||||
branch: '${{ steps.variables.outputs.BRANCH_NAME }}'
|
|
||||||
labels: |
|
|
||||||
Skip Changelog
|
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,6 +11,7 @@ __pycache__/
|
|||||||
env*/
|
env*/
|
||||||
dbt_env/
|
dbt_env/
|
||||||
build/
|
build/
|
||||||
|
!tests/functional/build
|
||||||
!core/dbt/docs/build
|
!core/dbt/docs/build
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
@@ -28,6 +29,8 @@ var/
|
|||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.dmypy.json
|
.dmypy.json
|
||||||
logs/
|
logs/
|
||||||
|
.user.yml
|
||||||
|
profiles.yml
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# Usually these files are written by a python script from a template
|
||||||
@@ -51,6 +54,7 @@ coverage.xml
|
|||||||
*,cover
|
*,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
test.env
|
test.env
|
||||||
|
makefile.test.env
|
||||||
*.pytest_cache/
|
*.pytest_cache/
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
# Configuration for pre-commit hooks (see https://pre-commit.com/).
|
# Configuration for pre-commit hooks (see https://pre-commit.com/).
|
||||||
# Eventually the hooks described here will be run as tests before merging each PR.
|
# Eventually the hooks described here will be run as tests before merging each PR.
|
||||||
|
|
||||||
# TODO: remove global exclusion of tests when testing overhaul is complete
|
exclude: ^(core/dbt/docs/build/|core/dbt/events/types_pb2.py)
|
||||||
exclude: ^(test/|core/dbt/docs/build/)
|
|
||||||
|
|
||||||
# Force all unspecified python hooks to run python 3.8
|
# Force all unspecified python hooks to run python 3.8
|
||||||
default_language_version:
|
default_language_version:
|
||||||
@@ -38,7 +37,7 @@ repos:
|
|||||||
alias: flake8-check
|
alias: flake8-check
|
||||||
stages: [manual]
|
stages: [manual]
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.942
|
rev: v1.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
# N.B.: Mypy is... a bit fragile.
|
# N.B.: Mypy is... a bit fragile.
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
- "Breaking changes" listed under a version may require action from end users or external maintainers when upgrading to that version.
|
- "Breaking changes" listed under a version may require action from end users or external maintainers when upgrading to that version.
|
||||||
- Do not edit this file directly. This file is auto-generated using [changie](https://github.com/miniscruff/changie). For details on how to document a change, see [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md#adding-changelog-entry)
|
- Do not edit this file directly. This file is auto-generated using [changie](https://github.com/miniscruff/changie). For details on how to document a change, see [the contributing guide](https://github.com/dbt-labs/dbt-core/blob/main/CONTRIBUTING.md#adding-changelog-entry)
|
||||||
|
|
||||||
|
|
||||||
## Previous Releases
|
## Previous Releases
|
||||||
|
|
||||||
For information on prior major and minor releases, see their changelogs:
|
For information on prior major and minor releases, see their changelogs:
|
||||||
|
|
||||||
|
|
||||||
|
* [1.6](https://github.com/dbt-labs/dbt-core/blob/1.6.latest/CHANGELOG.md)
|
||||||
|
* [1.5](https://github.com/dbt-labs/dbt-core/blob/1.5.latest/CHANGELOG.md)
|
||||||
|
* [1.4](https://github.com/dbt-labs/dbt-core/blob/1.4.latest/CHANGELOG.md)
|
||||||
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
* [1.3](https://github.com/dbt-labs/dbt-core/blob/1.3.latest/CHANGELOG.md)
|
||||||
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
* [1.2](https://github.com/dbt-labs/dbt-core/blob/1.2.latest/CHANGELOG.md)
|
||||||
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
* [1.1](https://github.com/dbt-labs/dbt-core/blob/1.1.latest/CHANGELOG.md)
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
1. [About this document](#about-this-document)
|
1. [About this document](#about-this-document)
|
||||||
2. [Getting the code](#getting-the-code)
|
2. [Getting the code](#getting-the-code)
|
||||||
3. [Setting up an environment](#setting-up-an-environment)
|
3. [Setting up an environment](#setting-up-an-environment)
|
||||||
4. [Running `dbt` in development](#running-dbt-core-in-development)
|
4. [Running dbt-core in development](#running-dbt-core-in-development)
|
||||||
5. [Testing dbt-core](#testing)
|
5. [Testing dbt-core](#testing)
|
||||||
6. [Debugging](#debugging)
|
6. [Debugging](#debugging)
|
||||||
7. [Adding a changelog entry](#adding-a-changelog-entry)
|
7. [Adding or modifying a changelog entry](#adding-or-modifying-a-changelog-entry)
|
||||||
8. [Submitting a Pull Request](#submitting-a-pull-request)
|
8. [Submitting a Pull Request](#submitting-a-pull-request)
|
||||||
|
|
||||||
## About this document
|
## About this document
|
||||||
@@ -56,7 +56,7 @@ There are some tools that will be helpful to you in developing locally. While th
|
|||||||
|
|
||||||
These are the tools used in `dbt-core` development and testing:
|
These are the tools used in `dbt-core` development and testing:
|
||||||
|
|
||||||
- [`tox`](https://tox.readthedocs.io/en/latest/) to manage virtualenvs across python versions. We currently target the latest patch releases for Python 3.7, 3.8, 3.9, and 3.10
|
- [`tox`](https://tox.readthedocs.io/en/latest/) to manage virtualenvs across python versions. We currently target the latest patch releases for Python 3.8, 3.9, 3.10 and 3.11
|
||||||
- [`pytest`](https://docs.pytest.org/en/latest/) to define, discover, and run tests
|
- [`pytest`](https://docs.pytest.org/en/latest/) to define, discover, and run tests
|
||||||
- [`flake8`](https://flake8.pycqa.org/en/latest/) for code linting
|
- [`flake8`](https://flake8.pycqa.org/en/latest/) for code linting
|
||||||
- [`black`](https://github.com/psf/black) for code formatting
|
- [`black`](https://github.com/psf/black) for code formatting
|
||||||
@@ -96,12 +96,15 @@ brew install postgresql
|
|||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
First make sure that you set up your `virtualenv` as described in [Setting up an environment](#setting-up-an-environment). Also ensure you have the latest version of pip installed with `pip install --upgrade pip`. Next, install `dbt-core` (and its dependencies) with:
|
First make sure that you set up your `virtualenv` as described in [Setting up an environment](#setting-up-an-environment). Also ensure you have the latest version of pip installed with `pip install --upgrade pip`. Next, install `dbt-core` (and its dependencies):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make dev
|
make dev
|
||||||
# or
|
```
|
||||||
|
or, alternatively:
|
||||||
|
```sh
|
||||||
pip install -r dev-requirements.txt -r editable-requirements.txt
|
pip install -r dev-requirements.txt -r editable-requirements.txt
|
||||||
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
When installed in this way, any changes you make to your local copy of the source code will be reflected immediately in your next `dbt` run.
|
When installed in this way, any changes you make to your local copy of the source code will be reflected immediately in your next `dbt` run.
|
||||||
@@ -110,7 +113,7 @@ When installed in this way, any changes you make to your local copy of the sourc
|
|||||||
|
|
||||||
With your virtualenv activated, the `dbt` script should point back to the source code you've cloned on your machine. You can verify this by running `which dbt`. This command should show you a path to an executable in your virtualenv.
|
With your virtualenv activated, the `dbt` script should point back to the source code you've cloned on your machine. You can verify this by running `which dbt`. This command should show you a path to an executable in your virtualenv.
|
||||||
|
|
||||||
Configure your [profile](https://docs.getdbt.com/docs/configure-your-profile) as necessary to connect to your target databases. It may be a good idea to add a new profile pointing to a local Postgres instance, or a specific test sandbox within your data warehouse if appropriate.
|
Configure your [profile](https://docs.getdbt.com/docs/configure-your-profile) as necessary to connect to your target databases. It may be a good idea to add a new profile pointing to a local Postgres instance, or a specific test sandbox within your data warehouse if appropriate. Make sure to create a profile before running integration tests.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -160,7 +163,7 @@ suites.
|
|||||||
|
|
||||||
#### `tox`
|
#### `tox`
|
||||||
|
|
||||||
[`tox`](https://tox.readthedocs.io/en/latest/) takes care of managing virtualenvs and install dependencies in order to run tests. You can also run tests in parallel, for example, you can run unit tests for Python 3.7, Python 3.8, Python 3.9, and Python 3.10 checks in parallel with `tox -p`. Also, you can run unit tests for specific python versions with `tox -e py37`. The configuration for these tests in located in `tox.ini`.
|
[`tox`](https://tox.readthedocs.io/en/latest/) takes care of managing virtualenvs and install dependencies in order to run tests. You can also run tests in parallel, for example, you can run unit tests for Python 3.8, Python 3.9, Python 3.10 and Python 3.11 checks in parallel with `tox -p`. Also, you can run unit tests for specific python versions with `tox -e py38`. The configuration for these tests in located in `tox.ini`.
|
||||||
|
|
||||||
#### `pytest`
|
#### `pytest`
|
||||||
|
|
||||||
@@ -168,12 +171,10 @@ Finally, you can also run a specific test or group of tests using [`pytest`](htt
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
# run all unit tests in a file
|
# run all unit tests in a file
|
||||||
python3 -m pytest test/unit/test_graph.py
|
python3 -m pytest tests/unit/test_graph.py
|
||||||
# run a specific unit test
|
# run a specific unit test
|
||||||
python3 -m pytest test/unit/test_graph.py::GraphTest::test__dependency_list
|
python3 -m pytest tests/unit/test_graph.py::GraphTest::test__dependency_list
|
||||||
# run specific Postgres integration tests (old way)
|
# run specific Postgres functional tests
|
||||||
python3 -m pytest -m profile_postgres test/integration/074_postgres_unlogged_table_tests
|
|
||||||
# run specific Postgres integration tests (new way)
|
|
||||||
python3 -m pytest tests/functional/sources
|
python3 -m pytest tests/functional/sources
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -182,9 +183,8 @@ python3 -m pytest tests/functional/sources
|
|||||||
### Unit, Integration, Functional?
|
### Unit, Integration, Functional?
|
||||||
|
|
||||||
Here are some general rules for adding tests:
|
Here are some general rules for adding tests:
|
||||||
* unit tests (`test/unit` & `tests/unit`) don’t need to access a database; "pure Python" tests should be written as unit tests
|
* unit tests (`tests/unit`) don’t need to access a database; "pure Python" tests should be written as unit tests
|
||||||
* functional tests (`test/integration` & `tests/functional`) cover anything that interacts with a database, namely adapter
|
* functional tests (`tests/functional`) cover anything that interacts with a database, namely adapter
|
||||||
* *everything in* `test/*` *is being steadily migrated to* `tests/*`
|
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
@@ -201,13 +201,21 @@ Here are some general rules for adding tests:
|
|||||||
* Sometimes flake8 complains about lines that are actually fine, in which case you can put a comment on the line such as: # noqa or # noqa: ANNN, where ANNN is the error code that flake8 issues.
|
* Sometimes flake8 complains about lines that are actually fine, in which case you can put a comment on the line such as: # noqa or # noqa: ANNN, where ANNN is the error code that flake8 issues.
|
||||||
* To collect output for `CProfile`, run dbt with the `-r` option and the name of an output file, i.e. `dbt -r dbt.cprof run`. If you just want to profile parsing, you can do: `dbt -r dbt.cprof parse`. `pip` install `snakeviz` to view the output. Run `snakeviz dbt.cprof` and output will be rendered in a browser window.
|
* To collect output for `CProfile`, run dbt with the `-r` option and the name of an output file, i.e. `dbt -r dbt.cprof run`. If you just want to profile parsing, you can do: `dbt -r dbt.cprof parse`. `pip` install `snakeviz` to view the output. Run `snakeviz dbt.cprof` and output will be rendered in a browser window.
|
||||||
|
|
||||||
## Adding a CHANGELOG Entry
|
## Adding or modifying a CHANGELOG Entry
|
||||||
|
|
||||||
We use [changie](https://changie.dev) to generate `CHANGELOG` entries. **Note:** Do not edit the `CHANGELOG.md` directly. Your modifications will be lost.
|
We use [changie](https://changie.dev) to generate `CHANGELOG` entries. **Note:** Do not edit the `CHANGELOG.md` directly. Your modifications will be lost.
|
||||||
|
|
||||||
Follow the steps to [install `changie`](https://changie.dev/guide/installation/) for your system.
|
Follow the steps to [install `changie`](https://changie.dev/guide/installation/) for your system.
|
||||||
|
|
||||||
Once changie is installed and your PR is created, simply run `changie new` and changie will walk you through the process of creating a changelog entry. Commit the file that's created and your changelog entry is complete!
|
Once changie is installed and your PR is created for a new feature, simply run the following command and changie will walk you through the process of creating a changelog entry:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
changie new
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit the file that's created and your changelog entry is complete!
|
||||||
|
|
||||||
|
If you are contributing to a feature already in progress, you will modify the changie yaml file in dbt/.changes/unreleased/ related to your change. If you need help finding this file, please ask within the discussion for the pull request!
|
||||||
|
|
||||||
You don't need to worry about which `dbt-core` version your change will go into. Just create the changelog entry with `changie`, and open your PR against the `main` branch. All merged changes will be included in the next minor version of `dbt-core`. The Core maintainers _may_ choose to "backport" specific changes in order to patch older minor versions. In that case, a maintainer will take care of that backport after merging your PR, before releasing the new version of `dbt-core`.
|
You don't need to worry about which `dbt-core` version your change will go into. Just create the changelog entry with `changie`, and open your PR against the `main` branch. All merged changes will be included in the next minor version of `dbt-core`. The Core maintainers _may_ choose to "backport" specific changes in order to patch older minor versions. In that case, a maintainer will take care of that backport after merging your PR, before releasing the new version of `dbt-core`.
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ ENV DEBIAN_FRONTEND noninteractive
|
|||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
software-properties-common \
|
software-properties-common gpg-agent \
|
||||||
&& add-apt-repository ppa:git-core/ppa -y \
|
&& add-apt-repository ppa:git-core/ppa -y \
|
||||||
&& apt-get dist-upgrade -y \
|
&& apt-get dist-upgrade -y \
|
||||||
&& apt-get install -y --no-install-recommends \
|
&& apt-get install -y --no-install-recommends \
|
||||||
@@ -30,16 +30,9 @@ RUN apt-get update \
|
|||||||
unixodbc-dev \
|
unixodbc-dev \
|
||||||
&& add-apt-repository ppa:deadsnakes/ppa \
|
&& add-apt-repository ppa:deadsnakes/ppa \
|
||||||
&& apt-get install -y \
|
&& apt-get install -y \
|
||||||
python \
|
python-is-python3 \
|
||||||
python-dev \
|
python-dev-is-python3 \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
python3.6 \
|
|
||||||
python3.6-dev \
|
|
||||||
python3-pip \
|
|
||||||
python3.6-venv \
|
|
||||||
python3.7 \
|
|
||||||
python3.7-dev \
|
|
||||||
python3.7-venv \
|
|
||||||
python3.8 \
|
python3.8 \
|
||||||
python3.8-dev \
|
python3.8-dev \
|
||||||
python3.8-venv \
|
python3.8-venv \
|
||||||
@@ -49,6 +42,9 @@ RUN apt-get update \
|
|||||||
python3.10 \
|
python3.10 \
|
||||||
python3.10-dev \
|
python3.10-dev \
|
||||||
python3.10-venv \
|
python3.10-venv \
|
||||||
|
python3.11 \
|
||||||
|
python3.11-dev \
|
||||||
|
python3.11-venv \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
|||||||
48
Makefile
48
Makefile
@@ -6,23 +6,41 @@ ifeq ($(USE_DOCKER),true)
|
|||||||
DOCKER_CMD := docker-compose run --rm test
|
DOCKER_CMD := docker-compose run --rm test
|
||||||
endif
|
endif
|
||||||
|
|
||||||
LOGS_DIR := ./logs
|
#
|
||||||
|
# To override CI_flags, create a file at this repo's root dir named `makefile.test.env`. Fill it
|
||||||
|
# with any ENV_VAR overrides required by your test environment, e.g.
|
||||||
|
# DBT_TEST_USER_1=user
|
||||||
|
# LOG_DIR="dir with a space in it"
|
||||||
|
#
|
||||||
|
# Warn: Restrict each line to one variable only.
|
||||||
|
#
|
||||||
|
ifeq (./makefile.test.env,$(wildcard ./makefile.test.env))
|
||||||
|
include ./makefile.test.env
|
||||||
|
endif
|
||||||
|
|
||||||
# Optional flag to invoke tests using our CI env.
|
|
||||||
# But we always want these active for structured
|
|
||||||
# log testing.
|
|
||||||
CI_FLAGS =\
|
CI_FLAGS =\
|
||||||
DBT_TEST_USER_1=dbt_test_user_1\
|
DBT_TEST_USER_1=$(if $(DBT_TEST_USER_1),$(DBT_TEST_USER_1),dbt_test_user_1)\
|
||||||
DBT_TEST_USER_2=dbt_test_user_2\
|
DBT_TEST_USER_2=$(if $(DBT_TEST_USER_2),$(DBT_TEST_USER_2),dbt_test_user_2)\
|
||||||
DBT_TEST_USER_3=dbt_test_user_3\
|
DBT_TEST_USER_3=$(if $(DBT_TEST_USER_3),$(DBT_TEST_USER_3),dbt_test_user_3)\
|
||||||
RUSTFLAGS="-D warnings"\
|
RUSTFLAGS=$(if $(RUSTFLAGS),$(RUSTFLAGS),"-D warnings")\
|
||||||
LOG_DIR=./logs\
|
LOG_DIR=$(if $(LOG_DIR),$(LOG_DIR),./logs)\
|
||||||
DBT_LOG_FORMAT=json
|
DBT_LOG_FORMAT=$(if $(DBT_LOG_FORMAT),$(DBT_LOG_FORMAT),json)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: dev_req
|
||||||
|
dev_req: ## Installs dbt-* packages in develop mode along with only development dependencies.
|
||||||
|
@\
|
||||||
|
pip install -r dev-requirements.txt
|
||||||
|
pip install -r editable-requirements.txt
|
||||||
|
|
||||||
.PHONY: dev
|
.PHONY: dev
|
||||||
dev: ## Installs dbt-* packages in develop mode along with development dependencies.
|
dev: dev_req ## Installs dbt-* packages in develop mode along with development dependencies and pre-commit.
|
||||||
@\
|
@\
|
||||||
pip install -r dev-requirements.txt -r editable-requirements.txt
|
pre-commit install
|
||||||
|
|
||||||
|
.PHONY: proto_types
|
||||||
|
proto_types: ## generates google protobuf python file from types.proto
|
||||||
|
protoc -I=./core/dbt/events --python_out=./core/dbt/events ./core/dbt/events/types.proto
|
||||||
|
|
||||||
.PHONY: mypy
|
.PHONY: mypy
|
||||||
mypy: .env ## Runs mypy against staged changes for static type checking.
|
mypy: .env ## Runs mypy against staged changes for static type checking.
|
||||||
@@ -61,7 +79,7 @@ test: .env ## Runs unit tests with py and code checks against staged changes.
|
|||||||
.PHONY: integration
|
.PHONY: integration
|
||||||
integration: .env ## Runs postgres integration tests with py-integration
|
integration: .env ## Runs postgres integration tests with py-integration
|
||||||
@\
|
@\
|
||||||
$(if $(USE_CI_FLAGS), $(CI_FLAGS)) $(DOCKER_CMD) tox -e py-integration -- -nauto
|
$(CI_FLAGS) $(DOCKER_CMD) tox -e py-integration -- -nauto
|
||||||
|
|
||||||
.PHONY: integration-fail-fast
|
.PHONY: integration-fail-fast
|
||||||
integration-fail-fast: .env ## Runs postgres integration tests with py-integration in "fail fast" mode.
|
integration-fail-fast: .env ## Runs postgres integration tests with py-integration in "fail fast" mode.
|
||||||
@@ -71,9 +89,9 @@ integration-fail-fast: .env ## Runs postgres integration tests with py-integrati
|
|||||||
.PHONY: interop
|
.PHONY: interop
|
||||||
interop: clean
|
interop: clean
|
||||||
@\
|
@\
|
||||||
mkdir $(LOGS_DIR) && \
|
mkdir $(LOG_DIR) && \
|
||||||
$(CI_FLAGS) $(DOCKER_CMD) tox -e py-integration -- -nauto && \
|
$(CI_FLAGS) $(DOCKER_CMD) tox -e py-integration -- -nauto && \
|
||||||
LOG_DIR=$(LOGS_DIR) cargo run --manifest-path test/interop/log_parsing/Cargo.toml
|
LOG_DIR=$(LOG_DIR) cargo run --manifest-path test/interop/log_parsing/Cargo.toml
|
||||||
|
|
||||||
.PHONY: setup-db
|
.PHONY: setup-db
|
||||||
setup-db: ## Setup Postgres database with docker-compose for system testing.
|
setup-db: ## Setup Postgres database with docker-compose for system testing.
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ These select statements, or "models", form a dbt project. Models frequently buil
|
|||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
- [Install dbt](https://docs.getdbt.com/docs/installation)
|
- [Install dbt](https://docs.getdbt.com/docs/get-started/installation)
|
||||||
- Read the [introduction](https://docs.getdbt.com/docs/introduction/) and [viewpoint](https://docs.getdbt.com/docs/about/viewpoint/)
|
- Read the [introduction](https://docs.getdbt.com/docs/introduction/) and [viewpoint](https://docs.getdbt.com/docs/about/viewpoint/)
|
||||||
|
|
||||||
## Join the dbt Community
|
## Join the dbt Community
|
||||||
|
|||||||
2
codecov.yml
Normal file
2
codecov.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
codecov:
|
||||||
|
branch: main # set new Default branch
|
||||||
@@ -2,50 +2,59 @@
|
|||||||
|
|
||||||
## The following are individual files in this directory.
|
## The following are individual files in this directory.
|
||||||
|
|
||||||
### deprecations.py
|
|
||||||
|
|
||||||
### flags.py
|
|
||||||
|
|
||||||
### main.py
|
|
||||||
|
|
||||||
### tracking.py
|
|
||||||
|
|
||||||
### version.py
|
|
||||||
|
|
||||||
### lib.py
|
|
||||||
|
|
||||||
### node_types.py
|
|
||||||
|
|
||||||
### helper_types.py
|
|
||||||
|
|
||||||
### links.py
|
|
||||||
|
|
||||||
### semver.py
|
|
||||||
|
|
||||||
### ui.py
|
|
||||||
|
|
||||||
### compilation.py
|
### compilation.py
|
||||||
|
|
||||||
|
### constants.py
|
||||||
|
|
||||||
### dataclass_schema.py
|
### dataclass_schema.py
|
||||||
|
|
||||||
|
### deprecations.py
|
||||||
|
|
||||||
### exceptions.py
|
### exceptions.py
|
||||||
|
|
||||||
|
### flags.py
|
||||||
|
|
||||||
|
### helper_types.py
|
||||||
|
|
||||||
### hooks.py
|
### hooks.py
|
||||||
|
|
||||||
|
### lib.py
|
||||||
|
|
||||||
|
### links.py
|
||||||
|
|
||||||
### logger.py
|
### logger.py
|
||||||
|
|
||||||
|
### main.py
|
||||||
|
|
||||||
|
### node_types.py
|
||||||
|
|
||||||
### profiler.py
|
### profiler.py
|
||||||
|
|
||||||
|
### selected_resources.py
|
||||||
|
|
||||||
|
### semver.py
|
||||||
|
|
||||||
|
### tracking.py
|
||||||
|
|
||||||
|
### ui.py
|
||||||
|
|
||||||
### utils.py
|
### utils.py
|
||||||
|
|
||||||
|
### version.py
|
||||||
|
|
||||||
|
|
||||||
## The subdirectories will be documented in a README in the subdirectory
|
## The subdirectories will be documented in a README in the subdirectory
|
||||||
* config
|
|
||||||
* include
|
|
||||||
* adapters
|
* adapters
|
||||||
* context
|
* cli
|
||||||
* deps
|
|
||||||
* graph
|
|
||||||
* task
|
|
||||||
* clients
|
* clients
|
||||||
|
* config
|
||||||
|
* context
|
||||||
|
* contracts
|
||||||
|
* deps
|
||||||
|
* docs
|
||||||
* events
|
* events
|
||||||
|
* graph
|
||||||
|
* include
|
||||||
|
* parser
|
||||||
|
* task
|
||||||
|
* tests
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
# these are all just exports, #noqa them so flake8 will be happy
|
# these are all just exports, #noqa them so flake8 will be happy
|
||||||
|
|
||||||
# TODO: Should we still include this in the `adapters` namespace?
|
# TODO: Should we still include this in the `adapters` namespace?
|
||||||
from dbt.contracts.connection import Credentials # noqa
|
from dbt.contracts.connection import Credentials # noqa: F401
|
||||||
from dbt.adapters.base.meta import available # noqa
|
from dbt.adapters.base.meta import available # noqa: F401
|
||||||
from dbt.adapters.base.connections import BaseConnectionManager # noqa
|
from dbt.adapters.base.connections import BaseConnectionManager # noqa: F401
|
||||||
from dbt.adapters.base.relation import ( # noqa
|
from dbt.adapters.base.relation import ( # noqa: F401
|
||||||
BaseRelation,
|
BaseRelation,
|
||||||
RelationType,
|
RelationType,
|
||||||
SchemaSearchMap,
|
SchemaSearchMap,
|
||||||
)
|
)
|
||||||
from dbt.adapters.base.column import Column # noqa
|
from dbt.adapters.base.column import Column # noqa: F401
|
||||||
from dbt.adapters.base.impl import AdapterConfig, BaseAdapter, PythonJobHelper # noqa
|
from dbt.adapters.base.impl import ( # noqa: F401
|
||||||
from dbt.adapters.base.plugin import AdapterPlugin # noqa
|
AdapterConfig,
|
||||||
|
BaseAdapter,
|
||||||
|
PythonJobHelper,
|
||||||
|
ConstraintSupport,
|
||||||
|
)
|
||||||
|
from dbt.adapters.base.plugin import AdapterPlugin # noqa: F401
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from dataclasses import dataclass
|
|||||||
import re
|
import re
|
||||||
from typing import Dict, ClassVar, Any, Optional
|
from typing import Dict, ClassVar, Any, Optional
|
||||||
|
|
||||||
from dbt.exceptions import RuntimeException
|
from dbt.exceptions import DbtRuntimeError
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -60,6 +60,7 @@ class Column:
|
|||||||
"float",
|
"float",
|
||||||
"double precision",
|
"double precision",
|
||||||
"float8",
|
"float8",
|
||||||
|
"double",
|
||||||
]
|
]
|
||||||
|
|
||||||
def is_integer(self) -> bool:
|
def is_integer(self) -> bool:
|
||||||
@@ -85,7 +86,7 @@ class Column:
|
|||||||
|
|
||||||
def string_size(self) -> int:
|
def string_size(self) -> int:
|
||||||
if not self.is_string():
|
if not self.is_string():
|
||||||
raise RuntimeException("Called string_size() on non-string field!")
|
raise DbtRuntimeError("Called string_size() on non-string field!")
|
||||||
|
|
||||||
if self.dtype == "text" or self.char_size is None:
|
if self.dtype == "text" or self.char_size is None:
|
||||||
# char_size should never be None. Handle it reasonably just in case
|
# char_size should never be None. Handle it reasonably just in case
|
||||||
@@ -124,7 +125,7 @@ class Column:
|
|||||||
def from_description(cls, name: str, raw_data_type: str) -> "Column":
|
def from_description(cls, name: str, raw_data_type: str) -> "Column":
|
||||||
match = re.match(r"([^(]+)(\([^)]+\))?", raw_data_type)
|
match = re.match(r"([^(]+)(\([^)]+\))?", raw_data_type)
|
||||||
if match is None:
|
if match is None:
|
||||||
raise RuntimeException(f'Could not interpret data type "{raw_data_type}"')
|
raise DbtRuntimeError(f'Could not interpret data type "{raw_data_type}"')
|
||||||
data_type, size_info = match.groups()
|
data_type, size_info = match.groups()
|
||||||
char_size = None
|
char_size = None
|
||||||
numeric_precision = None
|
numeric_precision = None
|
||||||
@@ -137,7 +138,7 @@ class Column:
|
|||||||
try:
|
try:
|
||||||
char_size = int(parts[0])
|
char_size = int(parts[0])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f'Could not interpret data_type "{raw_data_type}": '
|
f'Could not interpret data_type "{raw_data_type}": '
|
||||||
f'could not convert "{parts[0]}" to an integer'
|
f'could not convert "{parts[0]}" to an integer'
|
||||||
)
|
)
|
||||||
@@ -145,14 +146,14 @@ class Column:
|
|||||||
try:
|
try:
|
||||||
numeric_precision = int(parts[0])
|
numeric_precision = int(parts[0])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f'Could not interpret data_type "{raw_data_type}": '
|
f'Could not interpret data_type "{raw_data_type}": '
|
||||||
f'could not convert "{parts[0]}" to an integer'
|
f'could not convert "{parts[0]}" to an integer'
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
numeric_scale = int(parts[1])
|
numeric_scale = int(parts[1])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f'Could not interpret data_type "{raw_data_type}": '
|
f'Could not interpret data_type "{raw_data_type}": '
|
||||||
f'could not convert "{parts[1]}" to an integer'
|
f'could not convert "{parts[1]}" to an integer'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from dbt.events.types import (
|
|||||||
Rollback,
|
Rollback,
|
||||||
RollbackFailed,
|
RollbackFailed,
|
||||||
)
|
)
|
||||||
|
from dbt.events.contextvars import get_node_info
|
||||||
from dbt import flags
|
from dbt import flags
|
||||||
from dbt.utils import cast_to_str
|
from dbt.utils import cast_to_str
|
||||||
|
|
||||||
@@ -90,13 +91,13 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
key = self.get_thread_identifier()
|
key = self.get_thread_identifier()
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if key not in self.thread_connections:
|
if key not in self.thread_connections:
|
||||||
raise dbt.exceptions.InvalidConnectionException(key, list(self.thread_connections))
|
raise dbt.exceptions.InvalidConnectionError(key, list(self.thread_connections))
|
||||||
return self.thread_connections[key]
|
return self.thread_connections[key]
|
||||||
|
|
||||||
def set_thread_connection(self, conn: Connection) -> None:
|
def set_thread_connection(self, conn: Connection) -> None:
|
||||||
key = self.get_thread_identifier()
|
key = self.get_thread_identifier()
|
||||||
if key in self.thread_connections:
|
if key in self.thread_connections:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
"In set_thread_connection, existing connection exists for {}"
|
"In set_thread_connection, existing connection exists for {}"
|
||||||
)
|
)
|
||||||
self.thread_connections[key] = conn
|
self.thread_connections[key] = conn
|
||||||
@@ -136,47 +137,49 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
:return: A context manager that handles exceptions raised by the
|
:return: A context manager that handles exceptions raised by the
|
||||||
underlying database.
|
underlying database.
|
||||||
"""
|
"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError(
|
||||||
"`exception_handler` is not implemented for this adapter!"
|
"`exception_handler` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_connection_name(self, name: Optional[str] = None) -> Connection:
|
def set_connection_name(self, name: Optional[str] = None) -> Connection:
|
||||||
conn_name: str
|
"""Called by 'acquire_connection' in BaseAdapter, which is called by
|
||||||
if name is None:
|
'connection_named', called by 'connection_for(node)'.
|
||||||
# if a name isn't specified, we'll re-use a single handle
|
Creates a connection for this thread if one doesn't already
|
||||||
# named 'master'
|
exist, and will rename an existing connection."""
|
||||||
conn_name = "master"
|
|
||||||
else:
|
|
||||||
if not isinstance(name, str):
|
|
||||||
raise dbt.exceptions.CompilerException(
|
|
||||||
f"For connection name, got {name} - not a string!"
|
|
||||||
)
|
|
||||||
assert isinstance(name, str)
|
|
||||||
conn_name = name
|
|
||||||
|
|
||||||
|
conn_name: str = "master" if name is None else name
|
||||||
|
|
||||||
|
# Get a connection for this thread
|
||||||
conn = self.get_if_exists()
|
conn = self.get_if_exists()
|
||||||
|
|
||||||
|
if conn and conn.name == conn_name and conn.state == "open":
|
||||||
|
# Found a connection and nothing to do, so just return it
|
||||||
|
return conn
|
||||||
|
|
||||||
if conn is None:
|
if conn is None:
|
||||||
|
# Create a new connection
|
||||||
conn = Connection(
|
conn = Connection(
|
||||||
type=Identifier(self.TYPE),
|
type=Identifier(self.TYPE),
|
||||||
name=None,
|
name=conn_name,
|
||||||
state=ConnectionState.INIT,
|
state=ConnectionState.INIT,
|
||||||
transaction_open=False,
|
transaction_open=False,
|
||||||
handle=None,
|
handle=None,
|
||||||
credentials=self.profile.credentials,
|
credentials=self.profile.credentials,
|
||||||
)
|
)
|
||||||
self.set_thread_connection(conn)
|
|
||||||
|
|
||||||
if conn.name == conn_name and conn.state == "open":
|
|
||||||
return conn
|
|
||||||
|
|
||||||
fire_event(NewConnection(conn_name=conn_name, conn_type=self.TYPE))
|
|
||||||
|
|
||||||
if conn.state == "open":
|
|
||||||
fire_event(ConnectionReused(conn_name=conn_name))
|
|
||||||
else:
|
|
||||||
conn.handle = LazyHandle(self.open)
|
conn.handle = LazyHandle(self.open)
|
||||||
|
# Add the connection to thread_connections for this thread
|
||||||
|
self.set_thread_connection(conn)
|
||||||
|
fire_event(
|
||||||
|
NewConnection(conn_name=conn_name, conn_type=self.TYPE, node_info=get_node_info())
|
||||||
|
)
|
||||||
|
else: # existing connection either wasn't open or didn't have the right name
|
||||||
|
if conn.state != "open":
|
||||||
|
conn.handle = LazyHandle(self.open)
|
||||||
|
if conn.name != conn_name:
|
||||||
|
orig_conn_name: str = conn.name or ""
|
||||||
|
conn.name = conn_name
|
||||||
|
fire_event(ConnectionReused(orig_conn_name=orig_conn_name, conn_name=conn_name))
|
||||||
|
|
||||||
conn.name = conn_name
|
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -208,7 +211,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
connect should trigger a retry.
|
connect should trigger a retry.
|
||||||
:type retryable_exceptions: Iterable[Type[Exception]]
|
:type retryable_exceptions: Iterable[Type[Exception]]
|
||||||
:param int retry_limit: How many times to retry the call to connect. If this limit
|
:param int retry_limit: How many times to retry the call to connect. If this limit
|
||||||
is exceeded before a successful call, a FailedToConnectException will be raised.
|
is exceeded before a successful call, a FailedToConnectError will be raised.
|
||||||
Must be non-negative.
|
Must be non-negative.
|
||||||
:param retry_timeout: Time to wait between attempts to connect. Can also take a
|
:param retry_timeout: Time to wait between attempts to connect. Can also take a
|
||||||
Callable that takes the number of attempts so far, beginning at 0, and returns an int
|
Callable that takes the number of attempts so far, beginning at 0, and returns an int
|
||||||
@@ -217,14 +220,14 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
:param int _attempts: Parameter used to keep track of the number of attempts in calling the
|
:param int _attempts: Parameter used to keep track of the number of attempts in calling the
|
||||||
connect function across recursive calls. Passed as an argument to retry_timeout if it
|
connect function across recursive calls. Passed as an argument to retry_timeout if it
|
||||||
is a Callable. This parameter should not be set by the initial caller.
|
is a Callable. This parameter should not be set by the initial caller.
|
||||||
:raises dbt.exceptions.FailedToConnectException: Upon exhausting all retry attempts without
|
:raises dbt.exceptions.FailedToConnectError: Upon exhausting all retry attempts without
|
||||||
successfully acquiring a handle.
|
successfully acquiring a handle.
|
||||||
:return: The given connection with its appropriate state and handle attributes set
|
:return: The given connection with its appropriate state and handle attributes set
|
||||||
depending on whether we successfully acquired a handle or not.
|
depending on whether we successfully acquired a handle or not.
|
||||||
"""
|
"""
|
||||||
timeout = retry_timeout(_attempts) if callable(retry_timeout) else retry_timeout
|
timeout = retry_timeout(_attempts) if callable(retry_timeout) else retry_timeout
|
||||||
if timeout < 0:
|
if timeout < 0:
|
||||||
raise dbt.exceptions.FailedToConnectException(
|
raise dbt.exceptions.FailedToConnectError(
|
||||||
"retry_timeout cannot be negative or return a negative time."
|
"retry_timeout cannot be negative or return a negative time."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -232,7 +235,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
# This guard is not perfect others may add to the recursion limit (e.g. built-ins).
|
# This guard is not perfect others may add to the recursion limit (e.g. built-ins).
|
||||||
connection.handle = None
|
connection.handle = None
|
||||||
connection.state = ConnectionState.FAIL
|
connection.state = ConnectionState.FAIL
|
||||||
raise dbt.exceptions.FailedToConnectException("retry_limit cannot be negative")
|
raise dbt.exceptions.FailedToConnectError("retry_limit cannot be negative")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
connection.handle = connect()
|
connection.handle = connect()
|
||||||
@@ -243,7 +246,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
if retry_limit <= 0:
|
if retry_limit <= 0:
|
||||||
connection.handle = None
|
connection.handle = None
|
||||||
connection.state = ConnectionState.FAIL
|
connection.state = ConnectionState.FAIL
|
||||||
raise dbt.exceptions.FailedToConnectException(str(e))
|
raise dbt.exceptions.FailedToConnectError(str(e))
|
||||||
|
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Got a retryable error when attempting to open a {cls.TYPE} connection.\n"
|
f"Got a retryable error when attempting to open a {cls.TYPE} connection.\n"
|
||||||
@@ -265,12 +268,12 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
connection.handle = None
|
connection.handle = None
|
||||||
connection.state = ConnectionState.FAIL
|
connection.state = ConnectionState.FAIL
|
||||||
raise dbt.exceptions.FailedToConnectException(str(e))
|
raise dbt.exceptions.FailedToConnectError(str(e))
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def cancel_open(self) -> Optional[List[str]]:
|
def cancel_open(self) -> Optional[List[str]]:
|
||||||
"""Cancel all open connections on the adapter. (passable)"""
|
"""Cancel all open connections on the adapter. (passable)"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError(
|
||||||
"`cancel_open` is not implemented for this adapter!"
|
"`cancel_open` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -285,7 +288,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
This should be thread-safe, or hold the lock if necessary. The given
|
This should be thread-safe, or hold the lock if necessary. The given
|
||||||
connection should not be in either in_use or available.
|
connection should not be in either in_use or available.
|
||||||
"""
|
"""
|
||||||
raise dbt.exceptions.NotImplementedException("`open` is not implemented for this adapter!")
|
raise dbt.exceptions.NotImplementedError("`open` is not implemented for this adapter!")
|
||||||
|
|
||||||
def release(self) -> None:
|
def release(self) -> None:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -317,16 +320,12 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def begin(self) -> None:
|
def begin(self) -> None:
|
||||||
"""Begin a transaction. (passable)"""
|
"""Begin a transaction. (passable)"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`begin` is not implemented for this adapter!")
|
||||||
"`begin` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def commit(self) -> None:
|
def commit(self) -> None:
|
||||||
"""Commit a transaction. (passable)"""
|
"""Commit a transaction. (passable)"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`commit` is not implemented for this adapter!")
|
||||||
"`commit` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _rollback_handle(cls, connection: Connection) -> None:
|
def _rollback_handle(cls, connection: Connection) -> None:
|
||||||
@@ -336,7 +335,9 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
except Exception:
|
except Exception:
|
||||||
fire_event(
|
fire_event(
|
||||||
RollbackFailed(
|
RollbackFailed(
|
||||||
conn_name=cast_to_str(connection.name), exc_info=traceback.format_exc()
|
conn_name=cast_to_str(connection.name),
|
||||||
|
exc_info=traceback.format_exc(),
|
||||||
|
node_info=get_node_info(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -345,21 +346,27 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
"""Perform the actual close operation."""
|
"""Perform the actual close operation."""
|
||||||
# On windows, sometimes connection handles don't have a close() attr.
|
# On windows, sometimes connection handles don't have a close() attr.
|
||||||
if hasattr(connection.handle, "close"):
|
if hasattr(connection.handle, "close"):
|
||||||
fire_event(ConnectionClosed(conn_name=cast_to_str(connection.name)))
|
fire_event(
|
||||||
|
ConnectionClosed(conn_name=cast_to_str(connection.name), node_info=get_node_info())
|
||||||
|
)
|
||||||
connection.handle.close()
|
connection.handle.close()
|
||||||
else:
|
else:
|
||||||
fire_event(ConnectionLeftOpen(conn_name=cast_to_str(connection.name)))
|
fire_event(
|
||||||
|
ConnectionLeftOpen(
|
||||||
|
conn_name=cast_to_str(connection.name), node_info=get_node_info()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _rollback(cls, connection: Connection) -> None:
|
def _rollback(cls, connection: Connection) -> None:
|
||||||
"""Roll back the given connection."""
|
"""Roll back the given connection."""
|
||||||
if connection.transaction_open is False:
|
if connection.transaction_open is False:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
f"Tried to rollback transaction on connection "
|
f"Tried to rollback transaction on connection "
|
||||||
f'"{connection.name}", but it does not have one open!'
|
f'"{connection.name}", but it does not have one open!'
|
||||||
)
|
)
|
||||||
|
|
||||||
fire_event(Rollback(conn_name=cast_to_str(connection.name)))
|
fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
|
||||||
cls._rollback_handle(connection)
|
cls._rollback_handle(connection)
|
||||||
|
|
||||||
connection.transaction_open = False
|
connection.transaction_open = False
|
||||||
@@ -371,7 +378,7 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
return connection
|
return connection
|
||||||
|
|
||||||
if connection.transaction_open and connection.handle:
|
if connection.transaction_open and connection.handle:
|
||||||
fire_event(Rollback(conn_name=cast_to_str(connection.name)))
|
fire_event(Rollback(conn_name=cast_to_str(connection.name), node_info=get_node_info()))
|
||||||
cls._rollback_handle(connection)
|
cls._rollback_handle(connection)
|
||||||
connection.transaction_open = False
|
connection.transaction_open = False
|
||||||
|
|
||||||
@@ -404,6 +411,4 @@ class BaseConnectionManager(metaclass=abc.ABCMeta):
|
|||||||
:return: A tuple of the query status and results (empty if fetch=False).
|
:return: A tuple of the query status and results (empty if fetch=False).
|
||||||
:rtype: Tuple[AdapterResponse, agate.Table]
|
:rtype: Tuple[AdapterResponse, agate.Table]
|
||||||
"""
|
"""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`execute` is not implemented for this adapter!")
|
||||||
"`execute` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -2,45 +2,52 @@ import abc
|
|||||||
from concurrent.futures import as_completed, Future
|
from concurrent.futures import as_completed, Future
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
import time
|
import time
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import (
|
from typing import (
|
||||||
Optional,
|
|
||||||
Tuple,
|
|
||||||
Callable,
|
|
||||||
Iterable,
|
|
||||||
Type,
|
|
||||||
Dict,
|
|
||||||
Any,
|
Any,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
List,
|
List,
|
||||||
Mapping,
|
Mapping,
|
||||||
Iterator,
|
Optional,
|
||||||
Union,
|
|
||||||
Set,
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Type,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from dbt.contracts.graph.nodes import ColumnLevelConstraint, ConstraintType, ModelLevelConstraint
|
||||||
|
|
||||||
import agate
|
import agate
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from dbt.exceptions import (
|
from dbt.exceptions import (
|
||||||
raise_database_error,
|
DbtInternalError,
|
||||||
raise_compiler_error,
|
DbtRuntimeError,
|
||||||
invalid_type_error,
|
DbtValidationError,
|
||||||
get_relation_returned_multiple_results,
|
MacroArgTypeError,
|
||||||
InternalException,
|
MacroResultError,
|
||||||
NotImplementedException,
|
NotImplementedError,
|
||||||
RuntimeException,
|
NullRelationCacheAttemptedError,
|
||||||
|
NullRelationDropAttemptedError,
|
||||||
|
QuoteConfigTypeError,
|
||||||
|
RelationReturnedMultipleResultsError,
|
||||||
|
RenameToNoneAttemptedError,
|
||||||
|
SnapshotTargetIncompleteError,
|
||||||
|
SnapshotTargetNotSnapshotTableError,
|
||||||
|
UnexpectedNonTimestampError,
|
||||||
|
UnexpectedNullError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from dbt.adapters.protocol import (
|
from dbt.adapters.protocol import AdapterConfig, ConnectionManagerProtocol
|
||||||
AdapterConfig,
|
|
||||||
ConnectionManagerProtocol,
|
|
||||||
)
|
|
||||||
from dbt.clients.agate_helper import empty_table, merge_tables, table_from_rows
|
from dbt.clients.agate_helper import empty_table, merge_tables, table_from_rows
|
||||||
from dbt.clients.jinja import MacroGenerator
|
from dbt.clients.jinja import MacroGenerator
|
||||||
from dbt.contracts.graph.compiled import CompileResultNode, CompiledSeedNode
|
|
||||||
from dbt.contracts.graph.manifest import Manifest, MacroManifest
|
from dbt.contracts.graph.manifest import Manifest, MacroManifest
|
||||||
from dbt.contracts.graph.parsed import ParsedSeedNode
|
from dbt.contracts.graph.nodes import ResultNode
|
||||||
from dbt.events.functions import fire_event, warn_or_error
|
from dbt.events.functions import fire_event, warn_or_error
|
||||||
from dbt.events.types import (
|
from dbt.events.types import (
|
||||||
CacheMiss,
|
CacheMiss,
|
||||||
@@ -48,8 +55,10 @@ from dbt.events.types import (
|
|||||||
CodeExecution,
|
CodeExecution,
|
||||||
CodeExecutionStatus,
|
CodeExecutionStatus,
|
||||||
CatalogGenerationError,
|
CatalogGenerationError,
|
||||||
|
ConstraintNotSupported,
|
||||||
|
ConstraintNotEnforced,
|
||||||
)
|
)
|
||||||
from dbt.utils import filter_null_values, executor, cast_to_str
|
from dbt.utils import filter_null_values, executor, cast_to_str, AttrDict
|
||||||
|
|
||||||
from dbt.adapters.base.connections import Connection, AdapterResponse
|
from dbt.adapters.base.connections import Connection, AdapterResponse
|
||||||
from dbt.adapters.base.meta import AdapterMeta, available
|
from dbt.adapters.base.meta import AdapterMeta, available
|
||||||
@@ -61,19 +70,22 @@ from dbt.adapters.base.relation import (
|
|||||||
)
|
)
|
||||||
from dbt.adapters.base import Column as BaseColumn
|
from dbt.adapters.base import Column as BaseColumn
|
||||||
from dbt.adapters.base import Credentials
|
from dbt.adapters.base import Credentials
|
||||||
from dbt.adapters.cache import RelationsCache, _make_ref_key_msg
|
from dbt.adapters.cache import RelationsCache, _make_ref_key_dict
|
||||||
|
from dbt import deprecations
|
||||||
|
|
||||||
SeedModel = Union[ParsedSeedNode, CompiledSeedNode]
|
|
||||||
|
|
||||||
|
|
||||||
GET_CATALOG_MACRO_NAME = "get_catalog"
|
GET_CATALOG_MACRO_NAME = "get_catalog"
|
||||||
FRESHNESS_MACRO_NAME = "collect_freshness"
|
FRESHNESS_MACRO_NAME = "collect_freshness"
|
||||||
|
|
||||||
|
|
||||||
|
class ConstraintSupport(str, Enum):
|
||||||
|
ENFORCED = "enforced"
|
||||||
|
NOT_ENFORCED = "not_enforced"
|
||||||
|
NOT_SUPPORTED = "not_supported"
|
||||||
|
|
||||||
|
|
||||||
def _expect_row_value(key: str, row: agate.Row):
|
def _expect_row_value(key: str, row: agate.Row):
|
||||||
if key not in row.keys():
|
if key not in row.keys():
|
||||||
raise InternalException(
|
raise DbtInternalError(
|
||||||
'Got a row without "{}" column, columns: {}'.format(key, row.keys())
|
'Got a row without "{}" column, columns: {}'.format(key, row.keys())
|
||||||
)
|
)
|
||||||
return row[key]
|
return row[key]
|
||||||
@@ -102,18 +114,10 @@ def _utc(dt: Optional[datetime], source: BaseRelation, field_name: str) -> datet
|
|||||||
assume the datetime is already for UTC and add the timezone.
|
assume the datetime is already for UTC and add the timezone.
|
||||||
"""
|
"""
|
||||||
if dt is None:
|
if dt is None:
|
||||||
raise raise_database_error(
|
raise UnexpectedNullError(field_name, source)
|
||||||
"Expected a non-null value when querying field '{}' of table "
|
|
||||||
" {} but received value 'null' instead".format(field_name, source)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif not hasattr(dt, "tzinfo"):
|
elif not hasattr(dt, "tzinfo"):
|
||||||
raise raise_database_error(
|
raise UnexpectedNonTimestampError(field_name, source, dt)
|
||||||
"Expected a timestamp value when querying field '{}' of table "
|
|
||||||
"{} but received value of type '{}' instead".format(
|
|
||||||
field_name, source, type(dt).__name__
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif dt.tzinfo:
|
elif dt.tzinfo:
|
||||||
return dt.astimezone(pytz.UTC)
|
return dt.astimezone(pytz.UTC)
|
||||||
@@ -183,6 +187,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
- truncate_relation
|
- truncate_relation
|
||||||
- rename_relation
|
- rename_relation
|
||||||
- get_columns_in_relation
|
- get_columns_in_relation
|
||||||
|
- get_column_schema_from_query
|
||||||
- expand_column_types
|
- expand_column_types
|
||||||
- list_relations_without_caching
|
- list_relations_without_caching
|
||||||
- is_cancelable
|
- is_cancelable
|
||||||
@@ -209,6 +214,14 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
# for use in materializations
|
# for use in materializations
|
||||||
AdapterSpecificConfigs: Type[AdapterConfig] = AdapterConfig
|
AdapterSpecificConfigs: Type[AdapterConfig] = AdapterConfig
|
||||||
|
|
||||||
|
CONSTRAINT_SUPPORT = {
|
||||||
|
ConstraintType.check: ConstraintSupport.NOT_SUPPORTED,
|
||||||
|
ConstraintType.not_null: ConstraintSupport.ENFORCED,
|
||||||
|
ConstraintType.unique: ConstraintSupport.NOT_ENFORCED,
|
||||||
|
ConstraintType.primary_key: ConstraintSupport.NOT_ENFORCED,
|
||||||
|
ConstraintType.foreign_key: ConstraintSupport.ENFORCED,
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.cache = RelationsCache()
|
self.cache = RelationsCache()
|
||||||
@@ -243,9 +256,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
return conn.name
|
return conn.name
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def connection_named(
|
def connection_named(self, name: str, node: Optional[ResultNode] = None) -> Iterator[None]:
|
||||||
self, name: str, node: Optional[CompileResultNode] = None
|
|
||||||
) -> Iterator[None]:
|
|
||||||
try:
|
try:
|
||||||
if self.connections.query_header is not None:
|
if self.connections.query_header is not None:
|
||||||
self.connections.query_header.set(name, node)
|
self.connections.query_header.set(name, node)
|
||||||
@@ -257,13 +268,13 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
self.connections.query_header.reset()
|
self.connections.query_header.reset()
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def connection_for(self, node: CompileResultNode) -> Iterator[None]:
|
def connection_for(self, node: ResultNode) -> Iterator[None]:
|
||||||
with self.connection_named(node.unique_id, node):
|
with self.connection_named(node.unique_id, node):
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@available.parse(lambda *a, **k: ("", empty_table()))
|
@available.parse(lambda *a, **k: ("", empty_table()))
|
||||||
def execute(
|
def execute(
|
||||||
self, sql: str, auto_begin: bool = False, fetch: bool = False
|
self, sql: str, auto_begin: bool = False, fetch: bool = False, limit: Optional[int] = None
|
||||||
) -> Tuple[AdapterResponse, agate.Table]:
|
) -> Tuple[AdapterResponse, agate.Table]:
|
||||||
"""Execute the given SQL. This is a thin wrapper around
|
"""Execute the given SQL. This is a thin wrapper around
|
||||||
ConnectionManager.execute.
|
ConnectionManager.execute.
|
||||||
@@ -272,10 +283,35 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param bool auto_begin: If set, and dbt is not currently inside a
|
:param bool auto_begin: If set, and dbt is not currently inside a
|
||||||
transaction, automatically begin one.
|
transaction, automatically begin one.
|
||||||
:param bool fetch: If set, fetch results.
|
:param bool fetch: If set, fetch results.
|
||||||
|
:param Optional[int] limit: If set, only fetch n number of rows
|
||||||
:return: A tuple of the query status and results (empty if fetch=False).
|
:return: A tuple of the query status and results (empty if fetch=False).
|
||||||
:rtype: Tuple[AdapterResponse, agate.Table]
|
:rtype: Tuple[AdapterResponse, agate.Table]
|
||||||
"""
|
"""
|
||||||
return self.connections.execute(sql=sql, auto_begin=auto_begin, fetch=fetch)
|
return self.connections.execute(sql=sql, auto_begin=auto_begin, fetch=fetch, limit=limit)
|
||||||
|
|
||||||
|
def validate_sql(self, sql: str) -> AdapterResponse:
|
||||||
|
"""Submit the given SQL to the engine for validation, but not execution.
|
||||||
|
|
||||||
|
This should throw an appropriate exception if the input SQL is invalid, although
|
||||||
|
in practice that will generally be handled by delegating to an existing method
|
||||||
|
for execution and allowing the error handler to take care of the rest.
|
||||||
|
|
||||||
|
:param str sql: The sql to validate
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("`validate_sql` is not implemented for this adapter!")
|
||||||
|
|
||||||
|
@available.parse(lambda *a, **k: [])
|
||||||
|
def get_column_schema_from_query(self, sql: str) -> List[BaseColumn]:
|
||||||
|
"""Get a list of the Columns with names and data types from the given sql."""
|
||||||
|
_, cursor = self.connections.add_select_query(sql)
|
||||||
|
columns = [
|
||||||
|
self.Column.create(
|
||||||
|
column_name, self.connections.data_type_code_to_name(column_type_code)
|
||||||
|
)
|
||||||
|
# https://peps.python.org/pep-0249/#description
|
||||||
|
for column_name, column_type_code, *_ in cursor.description
|
||||||
|
]
|
||||||
|
return columns
|
||||||
|
|
||||||
@available.parse(lambda *a, **k: ("", empty_table()))
|
@available.parse(lambda *a, **k: ("", empty_table()))
|
||||||
def get_partitions_metadata(self, table: str) -> Tuple[agate.Table]:
|
def get_partitions_metadata(self, table: str) -> Tuple[agate.Table]:
|
||||||
@@ -359,7 +395,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
return {
|
return {
|
||||||
self.Relation.create_from(self.config, node).without_identifier()
|
self.Relation.create_from(self.config, node).without_identifier()
|
||||||
for node in manifest.nodes.values()
|
for node in manifest.nodes.values()
|
||||||
if (node.is_relational and not node.is_ephemeral_model)
|
if (node.is_relational and not node.is_ephemeral_model and not node.is_external_node)
|
||||||
}
|
}
|
||||||
|
|
||||||
def _get_catalog_schemas(self, manifest: Manifest) -> SchemaSearchMap:
|
def _get_catalog_schemas(self, manifest: Manifest) -> SchemaSearchMap:
|
||||||
@@ -372,7 +408,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
lowercase strings.
|
lowercase strings.
|
||||||
"""
|
"""
|
||||||
info_schema_name_map = SchemaSearchMap()
|
info_schema_name_map = SchemaSearchMap()
|
||||||
nodes: Iterator[CompileResultNode] = chain(
|
nodes: Iterator[ResultNode] = chain(
|
||||||
[
|
[
|
||||||
node
|
node
|
||||||
for node in manifest.nodes.values()
|
for node in manifest.nodes.values()
|
||||||
@@ -390,7 +426,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
return info_schema_name_map
|
return info_schema_name_map
|
||||||
|
|
||||||
def _relations_cache_for_schemas(
|
def _relations_cache_for_schemas(
|
||||||
self, manifest: Manifest, cache_schemas: Set[BaseRelation] = None
|
self, manifest: Manifest, cache_schemas: Optional[Set[BaseRelation]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Populate the relations cache for the given schemas. Returns an
|
"""Populate the relations cache for the given schemas. Returns an
|
||||||
iterable of the schemas populated, as strings.
|
iterable of the schemas populated, as strings.
|
||||||
@@ -426,7 +462,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
self,
|
self,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
clear: bool = False,
|
clear: bool = False,
|
||||||
required_schemas: Set[BaseRelation] = None,
|
required_schemas: Optional[Set[BaseRelation]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Run a query that gets a populated cache of the relations in the
|
"""Run a query that gets a populated cache of the relations in the
|
||||||
database and set the cache on this adapter.
|
database and set the cache on this adapter.
|
||||||
@@ -441,7 +477,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
"""Cache a new relation in dbt. It will show up in `list relations`."""
|
"""Cache a new relation in dbt. It will show up in `list relations`."""
|
||||||
if relation is None:
|
if relation is None:
|
||||||
name = self.nice_connection_name()
|
name = self.nice_connection_name()
|
||||||
raise_compiler_error("Attempted to cache a null relation for {}".format(name))
|
raise NullRelationCacheAttemptedError(name)
|
||||||
self.cache.add(relation)
|
self.cache.add(relation)
|
||||||
# so jinja doesn't render things
|
# so jinja doesn't render things
|
||||||
return ""
|
return ""
|
||||||
@@ -453,7 +489,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
"""
|
"""
|
||||||
if relation is None:
|
if relation is None:
|
||||||
name = self.nice_connection_name()
|
name = self.nice_connection_name()
|
||||||
raise_compiler_error("Attempted to drop a null relation for {}".format(name))
|
raise NullRelationDropAttemptedError(name)
|
||||||
self.cache.drop(relation)
|
self.cache.drop(relation)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@@ -470,9 +506,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
name = self.nice_connection_name()
|
name = self.nice_connection_name()
|
||||||
src_name = _relation_name(from_relation)
|
src_name = _relation_name(from_relation)
|
||||||
dst_name = _relation_name(to_relation)
|
dst_name = _relation_name(to_relation)
|
||||||
raise_compiler_error(
|
raise RenameToNoneAttemptedError(src_name, dst_name, name)
|
||||||
"Attempted to rename {} to {} for {}".format(src_name, dst_name, name)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache.rename(from_relation, to_relation)
|
self.cache.rename(from_relation, to_relation)
|
||||||
return ""
|
return ""
|
||||||
@@ -484,12 +518,12 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def date_function(cls) -> str:
|
def date_function(cls) -> str:
|
||||||
"""Get the date function used by this adapter's database."""
|
"""Get the date function used by this adapter's database."""
|
||||||
raise NotImplementedException("`date_function` is not implemented for this adapter!")
|
raise NotImplementedError("`date_function` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def is_cancelable(cls) -> bool:
|
def is_cancelable(cls) -> bool:
|
||||||
raise NotImplementedException("`is_cancelable` is not implemented for this adapter!")
|
raise NotImplementedError("`is_cancelable` is not implemented for this adapter!")
|
||||||
|
|
||||||
###
|
###
|
||||||
# Abstract methods about schemas
|
# Abstract methods about schemas
|
||||||
@@ -497,7 +531,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def list_schemas(self, database: str) -> List[str]:
|
def list_schemas(self, database: str) -> List[str]:
|
||||||
"""Get a list of existing schemas in database"""
|
"""Get a list of existing schemas in database"""
|
||||||
raise NotImplementedException("`list_schemas` is not implemented for this adapter!")
|
raise NotImplementedError("`list_schemas` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available.parse(lambda *a, **k: False)
|
@available.parse(lambda *a, **k: False)
|
||||||
def check_schema_exists(self, database: str, schema: str) -> bool:
|
def check_schema_exists(self, database: str, schema: str) -> bool:
|
||||||
@@ -520,13 +554,13 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
|
|
||||||
*Implementors must call self.cache.drop() to preserve cache state!*
|
*Implementors must call self.cache.drop() to preserve cache state!*
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`drop_relation` is not implemented for this adapter!")
|
raise NotImplementedError("`drop_relation` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
def truncate_relation(self, relation: BaseRelation) -> None:
|
def truncate_relation(self, relation: BaseRelation) -> None:
|
||||||
"""Truncate the given relation."""
|
"""Truncate the given relation."""
|
||||||
raise NotImplementedException("`truncate_relation` is not implemented for this adapter!")
|
raise NotImplementedError("`truncate_relation` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
@@ -535,15 +569,13 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
|
|
||||||
Implementors must call self.cache.rename() to preserve cache state.
|
Implementors must call self.cache.rename() to preserve cache state.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`rename_relation` is not implemented for this adapter!")
|
raise NotImplementedError("`rename_relation` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_list
|
@available.parse_list
|
||||||
def get_columns_in_relation(self, relation: BaseRelation) -> List[BaseColumn]:
|
def get_columns_in_relation(self, relation: BaseRelation) -> List[BaseColumn]:
|
||||||
"""Get a list of the columns in the given Relation."""
|
"""Get a list of the columns in the given Relation."""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError("`get_columns_in_relation` is not implemented for this adapter!")
|
||||||
"`get_columns_in_relation` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@available.deprecated("get_columns_in_relation", lambda *a, **k: [])
|
@available.deprecated("get_columns_in_relation", lambda *a, **k: [])
|
||||||
def get_columns_in_table(self, schema: str, identifier: str) -> List[BaseColumn]:
|
def get_columns_in_table(self, schema: str, identifier: str) -> List[BaseColumn]:
|
||||||
@@ -565,7 +597,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param self.Relation current: A relation that currently exists in the
|
:param self.Relation current: A relation that currently exists in the
|
||||||
database with columns of unspecified types.
|
database with columns of unspecified types.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError(
|
||||||
"`expand_target_column_types` is not implemented for this adapter!"
|
"`expand_target_column_types` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -580,7 +612,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:return: The relations in schema
|
:return: The relations in schema
|
||||||
:rtype: List[self.Relation]
|
:rtype: List[self.Relation]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError(
|
||||||
"`list_relations_without_caching` is not implemented for this adapter!"
|
"`list_relations_without_caching` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -622,7 +654,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
to_relation.
|
to_relation.
|
||||||
"""
|
"""
|
||||||
if not isinstance(from_relation, self.Relation):
|
if not isinstance(from_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="get_missing_columns",
|
method_name="get_missing_columns",
|
||||||
arg_name="from_relation",
|
arg_name="from_relation",
|
||||||
got_value=from_relation,
|
got_value=from_relation,
|
||||||
@@ -630,7 +662,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(to_relation, self.Relation):
|
if not isinstance(to_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="get_missing_columns",
|
method_name="get_missing_columns",
|
||||||
arg_name="to_relation",
|
arg_name="to_relation",
|
||||||
got_value=to_relation,
|
got_value=to_relation,
|
||||||
@@ -651,11 +683,11 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
expected columns.
|
expected columns.
|
||||||
|
|
||||||
:param Relation relation: The relation to check
|
:param Relation relation: The relation to check
|
||||||
:raises CompilationException: If the columns are
|
:raises InvalidMacroArgType: If the columns are
|
||||||
incorrect.
|
incorrect.
|
||||||
"""
|
"""
|
||||||
if not isinstance(relation, self.Relation):
|
if not isinstance(relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="valid_snapshot_target",
|
method_name="valid_snapshot_target",
|
||||||
arg_name="relation",
|
arg_name="relation",
|
||||||
got_value=relation,
|
got_value=relation,
|
||||||
@@ -676,24 +708,16 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
|
|
||||||
if missing:
|
if missing:
|
||||||
if extra:
|
if extra:
|
||||||
msg = (
|
raise SnapshotTargetIncompleteError(extra, missing)
|
||||||
'Snapshot target has ("{}") but not ("{}") - is it an '
|
|
||||||
"unmigrated previous version archive?".format(
|
|
||||||
'", "'.join(extra), '", "'.join(missing)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
msg = 'Snapshot target is not a snapshot table (missing "{}")'.format(
|
raise SnapshotTargetNotSnapshotTableError(missing)
|
||||||
'", "'.join(missing)
|
|
||||||
)
|
|
||||||
raise_compiler_error(msg)
|
|
||||||
|
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
def expand_target_column_types(
|
def expand_target_column_types(
|
||||||
self, from_relation: BaseRelation, to_relation: BaseRelation
|
self, from_relation: BaseRelation, to_relation: BaseRelation
|
||||||
) -> None:
|
) -> None:
|
||||||
if not isinstance(from_relation, self.Relation):
|
if not isinstance(from_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="expand_target_column_types",
|
method_name="expand_target_column_types",
|
||||||
arg_name="from_relation",
|
arg_name="from_relation",
|
||||||
got_value=from_relation,
|
got_value=from_relation,
|
||||||
@@ -701,7 +725,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(to_relation, self.Relation):
|
if not isinstance(to_relation, self.Relation):
|
||||||
invalid_type_error(
|
raise MacroArgTypeError(
|
||||||
method_name="expand_target_column_types",
|
method_name="expand_target_column_types",
|
||||||
arg_name="to_relation",
|
arg_name="to_relation",
|
||||||
got_value=to_relation,
|
got_value=to_relation,
|
||||||
@@ -724,11 +748,23 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
# we can't build the relations cache because we don't have a
|
# we can't build the relations cache because we don't have a
|
||||||
# manifest so we can't run any operations.
|
# manifest so we can't run any operations.
|
||||||
relations = self.list_relations_without_caching(schema_relation)
|
relations = self.list_relations_without_caching(schema_relation)
|
||||||
|
|
||||||
|
# if the cache is already populated, add this schema in
|
||||||
|
# otherwise, skip updating the cache and just ignore
|
||||||
|
if self.cache:
|
||||||
|
for relation in relations:
|
||||||
|
self.cache.add(relation)
|
||||||
|
if not relations:
|
||||||
|
# it's possible that there were no relations in some schemas. We want
|
||||||
|
# to insert the schemas we query into the cache's `.schemas` attribute
|
||||||
|
# so we can check it later
|
||||||
|
self.cache.update_schemas([(database, schema)])
|
||||||
|
|
||||||
fire_event(
|
fire_event(
|
||||||
ListRelations(
|
ListRelations(
|
||||||
database=cast_to_str(database),
|
database=cast_to_str(database),
|
||||||
schema=schema,
|
schema=schema,
|
||||||
relations=[_make_ref_key_msg(x) for x in relations],
|
relations=[_make_ref_key_dict(x) for x in relations],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -760,7 +796,6 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
schema: str,
|
schema: str,
|
||||||
identifier: str,
|
identifier: str,
|
||||||
) -> List[BaseRelation]:
|
) -> List[BaseRelation]:
|
||||||
|
|
||||||
matches = []
|
matches = []
|
||||||
|
|
||||||
search = self._make_match_kwargs(database, schema, identifier)
|
search = self._make_match_kwargs(database, schema, identifier)
|
||||||
@@ -783,7 +818,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
"schema": schema,
|
"schema": schema,
|
||||||
"database": database,
|
"database": database,
|
||||||
}
|
}
|
||||||
get_relation_returned_multiple_results(kwargs, matches)
|
raise RelationReturnedMultipleResultsError(kwargs, matches)
|
||||||
|
|
||||||
elif matches:
|
elif matches:
|
||||||
return matches[0]
|
return matches[0]
|
||||||
@@ -805,20 +840,20 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
@available.parse_none
|
@available.parse_none
|
||||||
def create_schema(self, relation: BaseRelation):
|
def create_schema(self, relation: BaseRelation):
|
||||||
"""Create the given schema if it does not exist."""
|
"""Create the given schema if it does not exist."""
|
||||||
raise NotImplementedException("`create_schema` is not implemented for this adapter!")
|
raise NotImplementedError("`create_schema` is not implemented for this adapter!")
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@available.parse_none
|
@available.parse_none
|
||||||
def drop_schema(self, relation: BaseRelation):
|
def drop_schema(self, relation: BaseRelation):
|
||||||
"""Drop the given schema (and everything in it) if it exists."""
|
"""Drop the given schema (and everything in it) if it exists."""
|
||||||
raise NotImplementedException("`drop_schema` is not implemented for this adapter!")
|
raise NotImplementedError("`drop_schema` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available
|
@available
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def quote(cls, identifier: str) -> str:
|
def quote(cls, identifier: str) -> str:
|
||||||
"""Quote the given identifier, as appropriate for the database."""
|
"""Quote the given identifier, as appropriate for the database."""
|
||||||
raise NotImplementedException("`quote` is not implemented for this adapter!")
|
raise NotImplementedError("`quote` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available
|
@available
|
||||||
def quote_as_configured(self, identifier: str, quote_key: str) -> str:
|
def quote_as_configured(self, identifier: str, quote_key: str) -> str:
|
||||||
@@ -847,10 +882,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
elif quote_config is None:
|
elif quote_config is None:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise_compiler_error(
|
raise QuoteConfigTypeError(quote_config)
|
||||||
f'The seed configuration value of "quote_columns" has an '
|
|
||||||
f"invalid type {type(quote_config)}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if quote_columns:
|
if quote_columns:
|
||||||
return self.quote(column)
|
return self.quote(column)
|
||||||
@@ -871,7 +903,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_text_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_text_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -883,7 +915,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_number_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_number_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -895,9 +927,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError("`convert_boolean_type` is not implemented for this adapter!")
|
||||||
"`convert_boolean_type` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -909,9 +939,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException(
|
raise NotImplementedError("`convert_datetime_type` is not implemented for this adapter!")
|
||||||
"`convert_datetime_type` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -923,7 +951,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_date_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_date_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@@ -935,7 +963,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
:param col_idx: The index into the agate table for the column.
|
:param col_idx: The index into the agate table for the column.
|
||||||
:return: The name of the type in the database
|
:return: The name of the type in the database
|
||||||
"""
|
"""
|
||||||
raise NotImplementedException("`convert_time_type` is not implemented for this adapter!")
|
raise NotImplementedError("`convert_time_type` is not implemented for this adapter!")
|
||||||
|
|
||||||
@available
|
@available
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -968,9 +996,9 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
manifest: Optional[Manifest] = None,
|
manifest: Optional[Manifest] = None,
|
||||||
project: Optional[str] = None,
|
project: Optional[str] = None,
|
||||||
context_override: Optional[Dict[str, Any]] = None,
|
context_override: Optional[Dict[str, Any]] = None,
|
||||||
kwargs: Dict[str, Any] = None,
|
kwargs: Optional[Dict[str, Any]] = None,
|
||||||
text_only_columns: Optional[Iterable[str]] = None,
|
text_only_columns: Optional[Iterable[str]] = None,
|
||||||
) -> agate.Table:
|
) -> AttrDict:
|
||||||
"""Look macro_name up in the manifest and execute its results.
|
"""Look macro_name up in the manifest and execute its results.
|
||||||
|
|
||||||
:param macro_name: The name of the macro to execute.
|
:param macro_name: The name of the macro to execute.
|
||||||
@@ -1002,7 +1030,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
else:
|
else:
|
||||||
package_name = 'the "{}" package'.format(project)
|
package_name = 'the "{}" package'.format(project)
|
||||||
|
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
'dbt could not find a macro with the name "{}" in {}'.format(
|
'dbt could not find a macro with the name "{}" in {}'.format(
|
||||||
macro_name, package_name
|
macro_name, package_name
|
||||||
)
|
)
|
||||||
@@ -1045,7 +1073,6 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
schemas: Set[str],
|
schemas: Set[str],
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
) -> agate.Table:
|
) -> agate.Table:
|
||||||
|
|
||||||
kwargs = {"information_schema": information_schema, "schemas": schemas}
|
kwargs = {"information_schema": information_schema, "schemas": schemas}
|
||||||
table = self.execute_macro(
|
table = self.execute_macro(
|
||||||
GET_CATALOG_MACRO_NAME,
|
GET_CATALOG_MACRO_NAME,
|
||||||
@@ -1055,7 +1082,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
manifest=manifest,
|
manifest=manifest,
|
||||||
)
|
)
|
||||||
|
|
||||||
results = self._catalog_filter_table(table, manifest)
|
results = self._catalog_filter_table(table, manifest) # type: ignore[arg-type]
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def get_catalog(self, manifest: Manifest) -> Tuple[agate.Table, List[Exception]]:
|
def get_catalog(self, manifest: Manifest) -> Tuple[agate.Table, List[Exception]]:
|
||||||
@@ -1087,7 +1114,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
loaded_at_field: str,
|
loaded_at_field: str,
|
||||||
filter: Optional[str],
|
filter: Optional[str],
|
||||||
manifest: Optional[Manifest] = None,
|
manifest: Optional[Manifest] = None,
|
||||||
) -> Dict[str, Any]:
|
) -> Tuple[Optional[AdapterResponse], Dict[str, Any]]:
|
||||||
"""Calculate the freshness of sources in dbt, and return it"""
|
"""Calculate the freshness of sources in dbt, and return it"""
|
||||||
kwargs: Dict[str, Any] = {
|
kwargs: Dict[str, Any] = {
|
||||||
"source": source,
|
"source": source,
|
||||||
@@ -1096,15 +1123,23 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# run the macro
|
# run the macro
|
||||||
table = self.execute_macro(FRESHNESS_MACRO_NAME, kwargs=kwargs, manifest=manifest)
|
# in older versions of dbt-core, the 'collect_freshness' macro returned the table of results directly
|
||||||
|
# starting in v1.5, by default, we return both the table and the adapter response (metadata about the query)
|
||||||
|
result: Union[
|
||||||
|
AttrDict, # current: contains AdapterResponse + agate.Table
|
||||||
|
agate.Table, # previous: just table
|
||||||
|
]
|
||||||
|
result = self.execute_macro(FRESHNESS_MACRO_NAME, kwargs=kwargs, manifest=manifest)
|
||||||
|
if isinstance(result, agate.Table):
|
||||||
|
deprecations.warn("collect-freshness-return-signature")
|
||||||
|
adapter_response = None
|
||||||
|
table = result
|
||||||
|
else:
|
||||||
|
adapter_response, table = result.response, result.table # type: ignore[attr-defined]
|
||||||
# now we have a 1-row table of the maximum `loaded_at_field` value and
|
# now we have a 1-row table of the maximum `loaded_at_field` value and
|
||||||
# the current time according to the db.
|
# the current time according to the db.
|
||||||
if len(table) != 1 or len(table[0]) != 2:
|
if len(table) != 1 or len(table[0]) != 2:
|
||||||
raise_compiler_error(
|
raise MacroResultError(FRESHNESS_MACRO_NAME, table)
|
||||||
'Got an invalid result from "{}" macro: {}'.format(
|
|
||||||
FRESHNESS_MACRO_NAME, [tuple(r) for r in table]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if table[0][0] is None:
|
if table[0][0] is None:
|
||||||
# no records in the table, so really the max_loaded_at was
|
# no records in the table, so really the max_loaded_at was
|
||||||
# infinitely long ago. Just call it 0:00 January 1 year UTC
|
# infinitely long ago. Just call it 0:00 January 1 year UTC
|
||||||
@@ -1114,11 +1149,12 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
|
|
||||||
snapshotted_at = _utc(table[0][1], source, loaded_at_field)
|
snapshotted_at = _utc(table[0][1], source, loaded_at_field)
|
||||||
age = (snapshotted_at - max_loaded_at).total_seconds()
|
age = (snapshotted_at - max_loaded_at).total_seconds()
|
||||||
return {
|
freshness = {
|
||||||
"max_loaded_at": max_loaded_at,
|
"max_loaded_at": max_loaded_at,
|
||||||
"snapshotted_at": snapshotted_at,
|
"snapshotted_at": snapshotted_at,
|
||||||
"age": age,
|
"age": age,
|
||||||
}
|
}
|
||||||
|
return adapter_response, freshness
|
||||||
|
|
||||||
def pre_model_hook(self, config: Mapping[str, Any]) -> Any:
|
def pre_model_hook(self, config: Mapping[str, Any]) -> Any:
|
||||||
"""A hook for running some operation before the model materialization
|
"""A hook for running some operation before the model materialization
|
||||||
@@ -1181,7 +1217,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
elif location == "prepend":
|
elif location == "prepend":
|
||||||
return f"'{value}' || {add_to}"
|
return f"'{value}' || {add_to}"
|
||||||
else:
|
else:
|
||||||
raise RuntimeException(f'Got an unexpected location value of "{location}"')
|
raise DbtRuntimeError(f'Got an unexpected location value of "{location}"')
|
||||||
|
|
||||||
def get_rows_different_sql(
|
def get_rows_different_sql(
|
||||||
self,
|
self,
|
||||||
@@ -1239,7 +1275,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
return self.generate_python_submission_response(submission_result)
|
return self.generate_python_submission_response(submission_result)
|
||||||
|
|
||||||
def generate_python_submission_response(self, submission_result: Any) -> AdapterResponse:
|
def generate_python_submission_response(self, submission_result: Any) -> AdapterResponse:
|
||||||
raise NotImplementedException(
|
raise NotImplementedError(
|
||||||
"Your adapter need to implement generate_python_submission_response"
|
"Your adapter need to implement generate_python_submission_response"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1263,7 +1299,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
valid_strategies.append("default")
|
valid_strategies.append("default")
|
||||||
builtin_strategies = self.builtin_incremental_strategies()
|
builtin_strategies = self.builtin_incremental_strategies()
|
||||||
if strategy in builtin_strategies and strategy not in valid_strategies:
|
if strategy in builtin_strategies and strategy not in valid_strategies:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f"The incremental strategy '{strategy}' is not valid for this adapter"
|
f"The incremental strategy '{strategy}' is not valid for this adapter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1271,7 +1307,7 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
macro_name = f"get_incremental_{strategy}_sql"
|
macro_name = f"get_incremental_{strategy}_sql"
|
||||||
# The model_context should have MacroGenerator callable objects for all macros
|
# The model_context should have MacroGenerator callable objects for all macros
|
||||||
if macro_name not in model_context:
|
if macro_name not in model_context:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
'dbt could not find an incremental strategy macro with the name "{}" in {}'.format(
|
'dbt could not find an incremental strategy macro with the name "{}" in {}'.format(
|
||||||
macro_name, self.config.project_name
|
macro_name, self.config.project_name
|
||||||
)
|
)
|
||||||
@@ -1280,6 +1316,119 @@ class BaseAdapter(metaclass=AdapterMeta):
|
|||||||
# This returns a callable macro
|
# This returns a callable macro
|
||||||
return model_context[macro_name]
|
return model_context[macro_name]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_column_constraint(cls, raw_constraint: Dict[str, Any]) -> ColumnLevelConstraint:
|
||||||
|
try:
|
||||||
|
ColumnLevelConstraint.validate(raw_constraint)
|
||||||
|
return ColumnLevelConstraint.from_dict(raw_constraint)
|
||||||
|
except Exception:
|
||||||
|
raise DbtValidationError(f"Could not parse constraint: {raw_constraint}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def render_column_constraint(cls, constraint: ColumnLevelConstraint) -> Optional[str]:
|
||||||
|
"""Render the given constraint as DDL text. Should be overriden by adapters which need custom constraint
|
||||||
|
rendering."""
|
||||||
|
constraint_expression = constraint.expression or ""
|
||||||
|
|
||||||
|
rendered_column_constraint = None
|
||||||
|
if constraint.type == ConstraintType.check and constraint_expression:
|
||||||
|
rendered_column_constraint = f"check ({constraint_expression})"
|
||||||
|
elif constraint.type == ConstraintType.not_null:
|
||||||
|
rendered_column_constraint = f"not null {constraint_expression}"
|
||||||
|
elif constraint.type == ConstraintType.unique:
|
||||||
|
rendered_column_constraint = f"unique {constraint_expression}"
|
||||||
|
elif constraint.type == ConstraintType.primary_key:
|
||||||
|
rendered_column_constraint = f"primary key {constraint_expression}"
|
||||||
|
elif constraint.type == ConstraintType.foreign_key and constraint_expression:
|
||||||
|
rendered_column_constraint = f"references {constraint_expression}"
|
||||||
|
elif constraint.type == ConstraintType.custom and constraint_expression:
|
||||||
|
rendered_column_constraint = constraint_expression
|
||||||
|
|
||||||
|
if rendered_column_constraint:
|
||||||
|
rendered_column_constraint = rendered_column_constraint.strip()
|
||||||
|
|
||||||
|
return rendered_column_constraint
|
||||||
|
|
||||||
|
@available
|
||||||
|
@classmethod
|
||||||
|
def render_raw_columns_constraints(cls, raw_columns: Dict[str, Dict[str, Any]]) -> List:
|
||||||
|
rendered_column_constraints = []
|
||||||
|
|
||||||
|
for v in raw_columns.values():
|
||||||
|
col_name = cls.quote(v["name"]) if v.get("quote") else v["name"]
|
||||||
|
rendered_column_constraint = [f"{col_name} {v['data_type']}"]
|
||||||
|
for con in v.get("constraints", None):
|
||||||
|
constraint = cls._parse_column_constraint(con)
|
||||||
|
c = cls.process_parsed_constraint(constraint, cls.render_column_constraint)
|
||||||
|
if c is not None:
|
||||||
|
rendered_column_constraint.append(c)
|
||||||
|
rendered_column_constraints.append(" ".join(rendered_column_constraint))
|
||||||
|
|
||||||
|
return rendered_column_constraints
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def process_parsed_constraint(
|
||||||
|
cls, parsed_constraint: Union[ColumnLevelConstraint, ModelLevelConstraint], render_func
|
||||||
|
) -> Optional[str]:
|
||||||
|
if (
|
||||||
|
parsed_constraint.warn_unsupported
|
||||||
|
and cls.CONSTRAINT_SUPPORT[parsed_constraint.type] == ConstraintSupport.NOT_SUPPORTED
|
||||||
|
):
|
||||||
|
warn_or_error(
|
||||||
|
ConstraintNotSupported(constraint=parsed_constraint.type.value, adapter=cls.type())
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
parsed_constraint.warn_unenforced
|
||||||
|
and cls.CONSTRAINT_SUPPORT[parsed_constraint.type] == ConstraintSupport.NOT_ENFORCED
|
||||||
|
):
|
||||||
|
warn_or_error(
|
||||||
|
ConstraintNotEnforced(constraint=parsed_constraint.type.value, adapter=cls.type())
|
||||||
|
)
|
||||||
|
if cls.CONSTRAINT_SUPPORT[parsed_constraint.type] != ConstraintSupport.NOT_SUPPORTED:
|
||||||
|
return render_func(parsed_constraint)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _parse_model_constraint(cls, raw_constraint: Dict[str, Any]) -> ModelLevelConstraint:
|
||||||
|
try:
|
||||||
|
ModelLevelConstraint.validate(raw_constraint)
|
||||||
|
c = ModelLevelConstraint.from_dict(raw_constraint)
|
||||||
|
return c
|
||||||
|
except Exception:
|
||||||
|
raise DbtValidationError(f"Could not parse constraint: {raw_constraint}")
|
||||||
|
|
||||||
|
@available
|
||||||
|
@classmethod
|
||||||
|
def render_raw_model_constraints(cls, raw_constraints: List[Dict[str, Any]]) -> List[str]:
|
||||||
|
return [c for c in map(cls.render_raw_model_constraint, raw_constraints) if c is not None]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def render_raw_model_constraint(cls, raw_constraint: Dict[str, Any]) -> Optional[str]:
|
||||||
|
constraint = cls._parse_model_constraint(raw_constraint)
|
||||||
|
return cls.process_parsed_constraint(constraint, cls.render_model_constraint)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def render_model_constraint(cls, constraint: ModelLevelConstraint) -> Optional[str]:
|
||||||
|
"""Render the given constraint as DDL text. Should be overriden by adapters which need custom constraint
|
||||||
|
rendering."""
|
||||||
|
constraint_prefix = f"constraint {constraint.name} " if constraint.name else ""
|
||||||
|
column_list = ", ".join(constraint.columns)
|
||||||
|
if constraint.type == ConstraintType.check and constraint.expression:
|
||||||
|
return f"{constraint_prefix}check ({constraint.expression})"
|
||||||
|
elif constraint.type == ConstraintType.unique:
|
||||||
|
constraint_expression = f" {constraint.expression}" if constraint.expression else ""
|
||||||
|
return f"{constraint_prefix}unique{constraint_expression} ({column_list})"
|
||||||
|
elif constraint.type == ConstraintType.primary_key:
|
||||||
|
constraint_expression = f" {constraint.expression}" if constraint.expression else ""
|
||||||
|
return f"{constraint_prefix}primary key{constraint_expression} ({column_list})"
|
||||||
|
elif constraint.type == ConstraintType.foreign_key and constraint.expression:
|
||||||
|
return f"{constraint_prefix}foreign key ({column_list}) references {constraint.expression}"
|
||||||
|
elif constraint.type == ConstraintType.custom and constraint.expression:
|
||||||
|
return f"{constraint_prefix}{constraint.expression}"
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
COLUMNS_EQUAL_SQL = """
|
COLUMNS_EQUAL_SQL = """
|
||||||
with diff_count as (
|
with diff_count as (
|
||||||
@@ -1313,7 +1462,6 @@ join diff_count using (id)
|
|||||||
def catch_as_completed(
|
def catch_as_completed(
|
||||||
futures, # typing: List[Future[agate.Table]]
|
futures, # typing: List[Future[agate.Table]]
|
||||||
) -> Tuple[agate.Table, List[Exception]]:
|
) -> Tuple[agate.Table, List[Exception]]:
|
||||||
|
|
||||||
# catalogs: agate.Table = agate.Table(rows=[])
|
# catalogs: agate.Table = agate.Table(rows=[])
|
||||||
tables: List[agate.Table] = []
|
tables: List[agate.Table] = []
|
||||||
exceptions: List[Exception] = []
|
exceptions: List[Exception] = []
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from typing import List, Optional, Type
|
from typing import List, Optional, Type
|
||||||
|
|
||||||
from dbt.adapters.base import Credentials
|
from dbt.adapters.base import Credentials
|
||||||
from dbt.exceptions import CompilationException
|
from dbt.exceptions import CompilationError
|
||||||
from dbt.adapters.protocol import AdapterProtocol
|
from dbt.adapters.protocol import AdapterProtocol
|
||||||
|
|
||||||
|
|
||||||
def project_name_from_path(include_path: str) -> str:
|
def project_name_from_path(include_path: str) -> str:
|
||||||
# avoid an import cycle
|
# avoid an import cycle
|
||||||
from dbt.config.project import Project
|
from dbt.config.project import PartialProject
|
||||||
|
|
||||||
partial = Project.partial_load(include_path)
|
partial = PartialProject.from_project_root(include_path)
|
||||||
if partial.project_name is None:
|
if partial.project_name is None:
|
||||||
raise CompilationException(f"Invalid project at {include_path}: name not set!")
|
raise CompilationError(f"Invalid project at {include_path}: name not set!")
|
||||||
return partial.project_name
|
return partial.project_name
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ from dbt.clients.jinja import QueryStringGenerator
|
|||||||
|
|
||||||
from dbt.context.manifest import generate_query_header_context
|
from dbt.context.manifest import generate_query_header_context
|
||||||
from dbt.contracts.connection import AdapterRequiredConfig, QueryComment
|
from dbt.contracts.connection import AdapterRequiredConfig, QueryComment
|
||||||
from dbt.contracts.graph.compiled import CompileResultNode
|
from dbt.contracts.graph.nodes import ResultNode
|
||||||
from dbt.contracts.graph.manifest import Manifest
|
from dbt.contracts.graph.manifest import Manifest
|
||||||
from dbt.exceptions import RuntimeException
|
from dbt.exceptions import DbtRuntimeError
|
||||||
|
|
||||||
|
|
||||||
class NodeWrapper:
|
class NodeWrapper:
|
||||||
@@ -48,7 +48,7 @@ class _QueryComment(local):
|
|||||||
if isinstance(comment, str) and "*/" in comment:
|
if isinstance(comment, str) and "*/" in comment:
|
||||||
# tell the user "no" so they don't hurt themselves by writing
|
# tell the user "no" so they don't hurt themselves by writing
|
||||||
# garbage
|
# garbage
|
||||||
raise RuntimeException(f'query comment contains illegal value "*/": {comment}')
|
raise DbtRuntimeError(f'query comment contains illegal value "*/": {comment}')
|
||||||
self.query_comment = comment
|
self.query_comment = comment
|
||||||
self.append = append
|
self.append = append
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class MacroQueryStringSetter:
|
|||||||
def reset(self):
|
def reset(self):
|
||||||
self.set("master", None)
|
self.set("master", None)
|
||||||
|
|
||||||
def set(self, name: str, node: Optional[CompileResultNode]):
|
def set(self, name: str, node: Optional[ResultNode]):
|
||||||
wrapped: Optional[NodeWrapper] = None
|
wrapped: Optional[NodeWrapper] = None
|
||||||
if node is not None:
|
if node is not None:
|
||||||
wrapped = NodeWrapper(node)
|
wrapped = NodeWrapper(node)
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
from collections.abc import Hashable
|
from collections.abc import Hashable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional, TypeVar, Any, Type, Dict, Union, Iterator, Tuple, Set
|
from typing import Optional, TypeVar, Any, Type, Dict, Iterator, Tuple, Set
|
||||||
|
|
||||||
from dbt.contracts.graph.compiled import CompiledNode
|
from dbt.contracts.graph.nodes import SourceDefinition, ManifestNode, ResultNode, ParsedNode
|
||||||
from dbt.contracts.graph.parsed import ParsedSourceDefinition, ParsedNode
|
|
||||||
from dbt.contracts.relation import (
|
from dbt.contracts.relation import (
|
||||||
RelationType,
|
RelationType,
|
||||||
ComponentName,
|
ComponentName,
|
||||||
@@ -12,7 +11,11 @@ from dbt.contracts.relation import (
|
|||||||
Policy,
|
Policy,
|
||||||
Path,
|
Path,
|
||||||
)
|
)
|
||||||
from dbt.exceptions import InternalException
|
from dbt.exceptions import (
|
||||||
|
ApproximateMatchError,
|
||||||
|
DbtInternalError,
|
||||||
|
MultipleDatabasesNotAllowedError,
|
||||||
|
)
|
||||||
from dbt.node_types import NodeType
|
from dbt.node_types import NodeType
|
||||||
from dbt.utils import filter_null_values, deep_merge, classproperty
|
from dbt.utils import filter_null_values, deep_merge, classproperty
|
||||||
|
|
||||||
@@ -27,8 +30,10 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
path: Path
|
path: Path
|
||||||
type: Optional[RelationType] = None
|
type: Optional[RelationType] = None
|
||||||
quote_character: str = '"'
|
quote_character: str = '"'
|
||||||
include_policy: Policy = Policy()
|
# Python 3.11 requires that these use default_factory instead of simple default
|
||||||
quote_policy: Policy = Policy()
|
# ValueError: mutable default <class 'dbt.contracts.relation.Policy'> for field include_policy is not allowed: use default_factory
|
||||||
|
include_policy: Policy = field(default_factory=lambda: Policy())
|
||||||
|
quote_policy: Policy = field(default_factory=lambda: Policy())
|
||||||
dbt_created: bool = False
|
dbt_created: bool = False
|
||||||
|
|
||||||
def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
|
def _is_exactish_match(self, field: ComponentName, value: str) -> bool:
|
||||||
@@ -39,9 +44,9 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_field_named(cls, field_name):
|
def _get_field_named(cls, field_name):
|
||||||
for field, _ in cls._get_fields():
|
for f, _ in cls._get_fields():
|
||||||
if field.name == field_name:
|
if f.name == field_name:
|
||||||
return field
|
return f
|
||||||
# this should be unreachable
|
# this should be unreachable
|
||||||
raise ValueError(f"BaseRelation has no {field_name} field!")
|
raise ValueError(f"BaseRelation has no {field_name} field!")
|
||||||
|
|
||||||
@@ -52,11 +57,11 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_quote_policy(cls) -> Policy:
|
def get_default_quote_policy(cls) -> Policy:
|
||||||
return cls._get_field_named("quote_policy").default
|
return cls._get_field_named("quote_policy").default_factory()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_default_include_policy(cls) -> Policy:
|
def get_default_include_policy(cls) -> Policy:
|
||||||
return cls._get_field_named("include_policy").default
|
return cls._get_field_named("include_policy").default_factory()
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
"""Override `.get` to return a metadata object so we don't break
|
"""Override `.get` to return a metadata object so we don't break
|
||||||
@@ -82,7 +87,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
if not search:
|
if not search:
|
||||||
# nothing was passed in
|
# nothing was passed in
|
||||||
raise dbt.exceptions.RuntimeException(
|
raise dbt.exceptions.DbtRuntimeError(
|
||||||
"Tried to match relation, but no search path was passed!"
|
"Tried to match relation, but no search path was passed!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,7 +104,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
|
|
||||||
if approximate_match and not exact_match:
|
if approximate_match and not exact_match:
|
||||||
target = self.create(database=database, schema=schema, identifier=identifier)
|
target = self.create(database=database, schema=schema, identifier=identifier)
|
||||||
dbt.exceptions.approximate_relation_match(target, self)
|
raise ApproximateMatchError(target, self)
|
||||||
|
|
||||||
return exact_match
|
return exact_match
|
||||||
|
|
||||||
@@ -184,7 +189,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_source(cls: Type[Self], source: ParsedSourceDefinition, **kwargs: Any) -> Self:
|
def create_from_source(cls: Type[Self], source: SourceDefinition, **kwargs: Any) -> Self:
|
||||||
source_quoting = source.quoting.to_dict(omit_none=True)
|
source_quoting = source.quoting.to_dict(omit_none=True)
|
||||||
source_quoting.pop("column", None)
|
source_quoting.pop("column", None)
|
||||||
quote_policy = deep_merge(
|
quote_policy = deep_merge(
|
||||||
@@ -209,7 +214,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def create_ephemeral_from_node(
|
def create_ephemeral_from_node(
|
||||||
cls: Type[Self],
|
cls: Type[Self],
|
||||||
config: HasQuoting,
|
config: HasQuoting,
|
||||||
node: Union[ParsedNode, CompiledNode],
|
node: ManifestNode,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
# Note that ephemeral models are based on the name.
|
# Note that ephemeral models are based on the name.
|
||||||
identifier = cls.add_ephemeral_prefix(node.name)
|
identifier = cls.add_ephemeral_prefix(node.name)
|
||||||
@@ -222,7 +227,7 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def create_from_node(
|
def create_from_node(
|
||||||
cls: Type[Self],
|
cls: Type[Self],
|
||||||
config: HasQuoting,
|
config: HasQuoting,
|
||||||
node: Union[ParsedNode, CompiledNode],
|
node,
|
||||||
quote_policy: Optional[Dict[str, bool]] = None,
|
quote_policy: Optional[Dict[str, bool]] = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
@@ -243,20 +248,20 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def create_from(
|
def create_from(
|
||||||
cls: Type[Self],
|
cls: Type[Self],
|
||||||
config: HasQuoting,
|
config: HasQuoting,
|
||||||
node: Union[CompiledNode, ParsedNode, ParsedSourceDefinition],
|
node: ResultNode,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Self:
|
) -> Self:
|
||||||
if node.resource_type == NodeType.Source:
|
if node.resource_type == NodeType.Source:
|
||||||
if not isinstance(node, ParsedSourceDefinition):
|
if not isinstance(node, SourceDefinition):
|
||||||
raise InternalException(
|
raise DbtInternalError(
|
||||||
"type mismatch, expected ParsedSourceDefinition but got {}".format(type(node))
|
"type mismatch, expected SourceDefinition but got {}".format(type(node))
|
||||||
)
|
)
|
||||||
return cls.create_from_source(node, **kwargs)
|
return cls.create_from_source(node, **kwargs)
|
||||||
else:
|
else:
|
||||||
if not isinstance(node, (ParsedNode, CompiledNode)):
|
# Can't use ManifestNode here because of parameterized generics
|
||||||
raise InternalException(
|
if not isinstance(node, (ParsedNode)):
|
||||||
"type mismatch, expected ParsedNode or CompiledNode but "
|
raise DbtInternalError(
|
||||||
"got {}".format(type(node))
|
f"type mismatch, expected ManifestNode but got {type(node)}"
|
||||||
)
|
)
|
||||||
return cls.create_from_node(config, node, **kwargs)
|
return cls.create_from_node(config, node, **kwargs)
|
||||||
|
|
||||||
@@ -323,6 +328,10 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def is_view(self) -> bool:
|
def is_view(self) -> bool:
|
||||||
return self.type == RelationType.View
|
return self.type == RelationType.View
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_materialized_view(self) -> bool:
|
||||||
|
return self.type == RelationType.MaterializedView
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def Table(cls) -> str:
|
def Table(cls) -> str:
|
||||||
return str(RelationType.Table)
|
return str(RelationType.Table)
|
||||||
@@ -339,6 +348,10 @@ class BaseRelation(FakeAPIObject, Hashable):
|
|||||||
def External(cls) -> str:
|
def External(cls) -> str:
|
||||||
return str(RelationType.External)
|
return str(RelationType.External)
|
||||||
|
|
||||||
|
@classproperty
|
||||||
|
def MaterializedView(cls) -> str:
|
||||||
|
return str(RelationType.MaterializedView)
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def get_relation_type(cls) -> Type[RelationType]:
|
def get_relation_type(cls) -> Type[RelationType]:
|
||||||
return RelationType
|
return RelationType
|
||||||
@@ -353,7 +366,7 @@ class InformationSchema(BaseRelation):
|
|||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
if not isinstance(self.information_schema_view, (type(None), str)):
|
if not isinstance(self.information_schema_view, (type(None), str)):
|
||||||
raise dbt.exceptions.CompilationException(
|
raise dbt.exceptions.CompilationError(
|
||||||
"Got an invalid name: {}".format(self.information_schema_view)
|
"Got an invalid name: {}".format(self.information_schema_view)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -437,7 +450,7 @@ class SchemaSearchMap(Dict[InformationSchema, Set[Optional[str]]]):
|
|||||||
if not allow_multiple_databases:
|
if not allow_multiple_databases:
|
||||||
seen = {r.database.lower() for r in self if r.database}
|
seen = {r.database.lower() for r in self if r.database}
|
||||||
if len(seen) > 1:
|
if len(seen) > 1:
|
||||||
dbt.exceptions.raise_compiler_error(str(seen))
|
raise MultipleDatabasesNotAllowedError(seen)
|
||||||
|
|
||||||
for information_schema_name, schema in self.search():
|
for information_schema_name, schema in self.search():
|
||||||
path = {"database": information_schema_name.database, "schema": schema}
|
path = {"database": information_schema_name.database, "schema": schema}
|
||||||
|
|||||||
@@ -1,32 +1,22 @@
|
|||||||
import re
|
|
||||||
import threading
|
import threading
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
|
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple
|
||||||
|
|
||||||
from dbt.adapters.reference_keys import (
|
from dbt.adapters.reference_keys import (
|
||||||
_make_ref_key,
|
_make_ref_key,
|
||||||
_make_ref_key_msg,
|
_make_ref_key_dict,
|
||||||
_make_msg_from_ref_key,
|
|
||||||
_ReferenceKey,
|
_ReferenceKey,
|
||||||
)
|
)
|
||||||
import dbt.exceptions
|
from dbt.exceptions import (
|
||||||
from dbt.events.functions import fire_event, fire_event_if
|
DependentLinkNotCachedError,
|
||||||
from dbt.events.types import (
|
NewNameAlreadyInCacheError,
|
||||||
AddLink,
|
NoneRelationFoundError,
|
||||||
AddRelation,
|
ReferencedLinkNotCachedError,
|
||||||
DropCascade,
|
TruncatedModelNameCausedCollisionError,
|
||||||
DropMissingRelation,
|
|
||||||
DropRelation,
|
|
||||||
DumpAfterAddGraph,
|
|
||||||
DumpAfterRenameSchema,
|
|
||||||
DumpBeforeAddGraph,
|
|
||||||
DumpBeforeRenameSchema,
|
|
||||||
RenameSchema,
|
|
||||||
TemporaryRelation,
|
|
||||||
UncachedRelation,
|
|
||||||
UpdateReference,
|
|
||||||
)
|
)
|
||||||
import dbt.flags as flags
|
from dbt.events.functions import fire_event, fire_event_if
|
||||||
|
from dbt.events.types import CacheAction, CacheDumpGraph
|
||||||
|
from dbt.flags import get_flags
|
||||||
from dbt.utils import lowercase
|
from dbt.utils import lowercase
|
||||||
|
|
||||||
|
|
||||||
@@ -150,11 +140,7 @@ class _CachedRelation:
|
|||||||
:raises InternalError: If the new key already exists.
|
:raises InternalError: If the new key already exists.
|
||||||
"""
|
"""
|
||||||
if new_key in self.referenced_by:
|
if new_key in self.referenced_by:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise NewNameAlreadyInCacheError(old_key, new_key)
|
||||||
'in rename of "{}" -> "{}", new name is in the cache already'.format(
|
|
||||||
old_key, new_key
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if old_key not in self.referenced_by:
|
if old_key not in self.referenced_by:
|
||||||
return
|
return
|
||||||
@@ -243,7 +229,7 @@ class RelationsCache:
|
|||||||
# self.relations or any cache entry's referenced_by during iteration
|
# self.relations or any cache entry's referenced_by during iteration
|
||||||
# it's a runtime error!
|
# it's a runtime error!
|
||||||
with self.lock:
|
with self.lock:
|
||||||
return {dot_separated(k): v.dump_graph_entry() for k, v in self.relations.items()}
|
return {dot_separated(k): str(v.dump_graph_entry()) for k, v in self.relations.items()}
|
||||||
|
|
||||||
def _setdefault(self, relation: _CachedRelation):
|
def _setdefault(self, relation: _CachedRelation):
|
||||||
"""Add a relation to the cache, or return it if it already exists.
|
"""Add a relation to the cache, or return it if it already exists.
|
||||||
@@ -270,21 +256,17 @@ class RelationsCache:
|
|||||||
if referenced is None:
|
if referenced is None:
|
||||||
return
|
return
|
||||||
if referenced is None:
|
if referenced is None:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise ReferencedLinkNotCachedError(referenced_key)
|
||||||
"in add_link, referenced link key {} not in cache!".format(referenced_key)
|
|
||||||
)
|
|
||||||
|
|
||||||
dependent = self.relations.get(dependent_key)
|
dependent = self.relations.get(dependent_key)
|
||||||
if dependent is None:
|
if dependent is None:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise DependentLinkNotCachedError(dependent_key)
|
||||||
"in add_link, dependent link key {} not in cache!".format(dependent_key)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert dependent is not None # we just raised!
|
assert dependent is not None # we just raised!
|
||||||
|
|
||||||
referenced.add_reference(dependent)
|
referenced.add_reference(dependent)
|
||||||
|
|
||||||
# TODO: Is this dead code? I can't seem to find it grepping the codebase.
|
# This is called in plugins/postgres/dbt/adapters/postgres/impl.py
|
||||||
def add_link(self, referenced, dependent):
|
def add_link(self, referenced, dependent):
|
||||||
"""Add a link between two relations to the database. If either relation
|
"""Add a link between two relations to the database. If either relation
|
||||||
does not exist, it will be added as an "external" relation.
|
does not exist, it will be added as an "external" relation.
|
||||||
@@ -306,9 +288,9 @@ class RelationsCache:
|
|||||||
# referring to a table outside our control. There's no need to make
|
# referring to a table outside our control. There's no need to make
|
||||||
# a link - we will never drop the referenced relation during a run.
|
# a link - we will never drop the referenced relation during a run.
|
||||||
fire_event(
|
fire_event(
|
||||||
UncachedRelation(
|
CacheAction(
|
||||||
dep_key=_make_msg_from_ref_key(dep_key),
|
ref_key=ref_key._asdict(),
|
||||||
ref_key=_make_msg_from_ref_key(ref_key),
|
ref_key_2=dep_key._asdict(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@@ -321,8 +303,10 @@ class RelationsCache:
|
|||||||
dependent = dependent.replace(type=referenced.External)
|
dependent = dependent.replace(type=referenced.External)
|
||||||
self.add(dependent)
|
self.add(dependent)
|
||||||
fire_event(
|
fire_event(
|
||||||
AddLink(
|
CacheAction(
|
||||||
dep_key=_make_msg_from_ref_key(dep_key), ref_key=_make_msg_from_ref_key(ref_key)
|
action="add_link",
|
||||||
|
ref_key=dep_key._asdict(),
|
||||||
|
ref_key_2=ref_key._asdict(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -334,13 +318,20 @@ class RelationsCache:
|
|||||||
|
|
||||||
:param BaseRelation relation: The underlying relation.
|
:param BaseRelation relation: The underlying relation.
|
||||||
"""
|
"""
|
||||||
|
flags = get_flags()
|
||||||
cached = _CachedRelation(relation)
|
cached = _CachedRelation(relation)
|
||||||
fire_event(AddRelation(relation=_make_ref_key_msg(cached)))
|
fire_event_if(
|
||||||
fire_event_if(flags.LOG_CACHE_EVENTS, lambda: DumpBeforeAddGraph(dump=self.dump_graph()))
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="before", action="adding", dump=self.dump_graph()),
|
||||||
|
)
|
||||||
|
fire_event(CacheAction(action="add_relation", ref_key=_make_ref_key_dict(cached)))
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self._setdefault(cached)
|
self._setdefault(cached)
|
||||||
fire_event_if(flags.LOG_CACHE_EVENTS, lambda: DumpAfterAddGraph(dump=self.dump_graph()))
|
fire_event_if(
|
||||||
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="after", action="adding", dump=self.dump_graph()),
|
||||||
|
)
|
||||||
|
|
||||||
def _remove_refs(self, keys):
|
def _remove_refs(self, keys):
|
||||||
"""Removes all references to all entries in keys. This does not
|
"""Removes all references to all entries in keys. This does not
|
||||||
@@ -367,17 +358,20 @@ class RelationsCache:
|
|||||||
:param str identifier: The identifier of the relation to drop.
|
:param str identifier: The identifier of the relation to drop.
|
||||||
"""
|
"""
|
||||||
dropped_key = _make_ref_key(relation)
|
dropped_key = _make_ref_key(relation)
|
||||||
dropped_key_msg = _make_ref_key_msg(relation)
|
dropped_key_msg = _make_ref_key_dict(relation)
|
||||||
fire_event(DropRelation(dropped=dropped_key_msg))
|
fire_event(CacheAction(action="drop_relation", ref_key=dropped_key_msg))
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if dropped_key not in self.relations:
|
if dropped_key not in self.relations:
|
||||||
fire_event(DropMissingRelation(relation=dropped_key_msg))
|
fire_event(CacheAction(action="drop_missing_relation", ref_key=dropped_key_msg))
|
||||||
return
|
return
|
||||||
consequences = self.relations[dropped_key].collect_consequences()
|
consequences = self.relations[dropped_key].collect_consequences()
|
||||||
# convert from a list of _ReferenceKeys to a list of ReferenceKeyMsgs
|
# convert from a list of _ReferenceKeys to a list of ReferenceKeyMsgs
|
||||||
consequence_msgs = [_make_msg_from_ref_key(key) for key in consequences]
|
consequence_msgs = [key._asdict() for key in consequences]
|
||||||
|
fire_event(
|
||||||
fire_event(DropCascade(dropped=dropped_key_msg, consequences=consequence_msgs))
|
CacheAction(
|
||||||
|
action="drop_cascade", ref_key=dropped_key_msg, ref_list=consequence_msgs
|
||||||
|
)
|
||||||
|
)
|
||||||
self._remove_refs(consequences)
|
self._remove_refs(consequences)
|
||||||
|
|
||||||
def _rename_relation(self, old_key, new_relation):
|
def _rename_relation(self, old_key, new_relation):
|
||||||
@@ -400,12 +394,14 @@ class RelationsCache:
|
|||||||
for cached in self.relations.values():
|
for cached in self.relations.values():
|
||||||
if cached.is_referenced_by(old_key):
|
if cached.is_referenced_by(old_key):
|
||||||
fire_event(
|
fire_event(
|
||||||
UpdateReference(
|
CacheAction(
|
||||||
old_key=_make_ref_key_msg(old_key),
|
action="update_reference",
|
||||||
new_key=_make_ref_key_msg(new_key),
|
ref_key=_make_ref_key_dict(old_key),
|
||||||
cached_key=_make_ref_key_msg(cached.key()),
|
ref_key_2=_make_ref_key_dict(new_key),
|
||||||
|
ref_key_3=_make_ref_key_dict(cached.key()),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
cached.rename_key(old_key, new_key)
|
cached.rename_key(old_key, new_key)
|
||||||
|
|
||||||
self.relations[new_key] = relation
|
self.relations[new_key] = relation
|
||||||
@@ -430,27 +426,10 @@ class RelationsCache:
|
|||||||
if new_key in self.relations:
|
if new_key in self.relations:
|
||||||
# Tell user when collision caused by model names truncated during
|
# Tell user when collision caused by model names truncated during
|
||||||
# materialization.
|
# materialization.
|
||||||
match = re.search("__dbt_backup|__dbt_tmp$", new_key.identifier)
|
raise TruncatedModelNameCausedCollisionError(new_key, self.relations)
|
||||||
if match:
|
|
||||||
truncated_model_name_prefix = new_key.identifier[: match.start()]
|
|
||||||
message_addendum = (
|
|
||||||
"\n\nName collisions can occur when the length of two "
|
|
||||||
"models' names approach your database's builtin limit. "
|
|
||||||
"Try restructuring your project such that no two models "
|
|
||||||
"share the prefix '{}'.".format(truncated_model_name_prefix)
|
|
||||||
+ " Then, clean your warehouse of any removed models."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
message_addendum = ""
|
|
||||||
|
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
|
||||||
"in rename, new key {} already in cache: {}{}".format(
|
|
||||||
new_key, list(self.relations.keys()), message_addendum
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if old_key not in self.relations:
|
if old_key not in self.relations:
|
||||||
fire_event(TemporaryRelation(key=_make_msg_from_ref_key(old_key)))
|
fire_event(CacheAction(action="temporary_relation", ref_key=old_key._asdict()))
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -469,13 +448,16 @@ class RelationsCache:
|
|||||||
old_key = _make_ref_key(old)
|
old_key = _make_ref_key(old)
|
||||||
new_key = _make_ref_key(new)
|
new_key = _make_ref_key(new)
|
||||||
fire_event(
|
fire_event(
|
||||||
RenameSchema(
|
CacheAction(
|
||||||
old_key=_make_msg_from_ref_key(old_key), new_key=_make_msg_from_ref_key(new)
|
action="rename_relation",
|
||||||
|
ref_key=old_key._asdict(),
|
||||||
|
ref_key_2=new_key._asdict(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
flags = get_flags()
|
||||||
fire_event_if(
|
fire_event_if(
|
||||||
flags.LOG_CACHE_EVENTS, lambda: DumpBeforeRenameSchema(dump=self.dump_graph())
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="before", action="rename", dump=self.dump_graph()),
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
@@ -485,7 +467,8 @@ class RelationsCache:
|
|||||||
self._setdefault(_CachedRelation(new))
|
self._setdefault(_CachedRelation(new))
|
||||||
|
|
||||||
fire_event_if(
|
fire_event_if(
|
||||||
flags.LOG_CACHE_EVENTS, lambda: DumpAfterRenameSchema(dump=self.dump_graph())
|
flags.LOG_CACHE_EVENTS,
|
||||||
|
lambda: CacheDumpGraph(before_after="after", action="rename", dump=self.dump_graph()),
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_relations(self, database: Optional[str], schema: Optional[str]) -> List[Any]:
|
def get_relations(self, database: Optional[str], schema: Optional[str]) -> List[Any]:
|
||||||
@@ -505,9 +488,7 @@ class RelationsCache:
|
|||||||
]
|
]
|
||||||
|
|
||||||
if None in results:
|
if None in results:
|
||||||
dbt.exceptions.raise_cache_inconsistent(
|
raise NoneRelationFoundError()
|
||||||
"in get_relations, a None relation was found in the cache!"
|
|
||||||
)
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
|||||||
@@ -9,10 +9,11 @@ from dbt.adapters.base.plugin import AdapterPlugin
|
|||||||
from dbt.adapters.protocol import AdapterConfig, AdapterProtocol, RelationProtocol
|
from dbt.adapters.protocol import AdapterConfig, AdapterProtocol, RelationProtocol
|
||||||
from dbt.contracts.connection import AdapterRequiredConfig, Credentials
|
from dbt.contracts.connection import AdapterRequiredConfig, Credentials
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import AdapterImportError, PluginLoadError
|
from dbt.events.types import AdapterImportError, PluginLoadError, AdapterRegistered
|
||||||
from dbt.exceptions import InternalException, RuntimeException
|
from dbt.exceptions import DbtInternalError, DbtRuntimeError
|
||||||
from dbt.include.global_project import PACKAGE_PATH as GLOBAL_PROJECT_PATH
|
from dbt.include.global_project import PACKAGE_PATH as GLOBAL_PROJECT_PATH
|
||||||
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
from dbt.include.global_project import PROJECT_NAME as GLOBAL_PROJECT_NAME
|
||||||
|
from dbt.semver import VersionSpecifier
|
||||||
|
|
||||||
Adapter = AdapterProtocol
|
Adapter = AdapterProtocol
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ class AdapterContainer:
|
|||||||
names = ", ".join(self.plugins.keys())
|
names = ", ".join(self.plugins.keys())
|
||||||
|
|
||||||
message = f"Invalid adapter type {name}! Must be one of {names}"
|
message = f"Invalid adapter type {name}! Must be one of {names}"
|
||||||
raise RuntimeException(message)
|
raise DbtRuntimeError(message)
|
||||||
|
|
||||||
def get_adapter_class_by_name(self, name: str) -> Type[Adapter]:
|
def get_adapter_class_by_name(self, name: str) -> Type[Adapter]:
|
||||||
plugin = self.get_plugin_by_name(name)
|
plugin = self.get_plugin_by_name(name)
|
||||||
@@ -60,7 +61,7 @@ class AdapterContainer:
|
|||||||
# the user about it via a runtime error
|
# the user about it via a runtime error
|
||||||
if exc.name == "dbt.adapters." + name:
|
if exc.name == "dbt.adapters." + name:
|
||||||
fire_event(AdapterImportError(exc=str(exc)))
|
fire_event(AdapterImportError(exc=str(exc)))
|
||||||
raise RuntimeException(f"Could not find adapter type {name}!")
|
raise DbtRuntimeError(f"Could not find adapter type {name}!")
|
||||||
# otherwise, the error had to have come from some underlying
|
# otherwise, the error had to have come from some underlying
|
||||||
# library. Log the stack trace.
|
# library. Log the stack trace.
|
||||||
|
|
||||||
@@ -70,7 +71,7 @@ class AdapterContainer:
|
|||||||
plugin_type = plugin.adapter.type()
|
plugin_type = plugin.adapter.type()
|
||||||
|
|
||||||
if plugin_type != name:
|
if plugin_type != name:
|
||||||
raise RuntimeException(
|
raise DbtRuntimeError(
|
||||||
f"Expected to find adapter with type named {name}, got "
|
f"Expected to find adapter with type named {name}, got "
|
||||||
f"adapter with type {plugin_type}"
|
f"adapter with type {plugin_type}"
|
||||||
)
|
)
|
||||||
@@ -89,7 +90,13 @@ class AdapterContainer:
|
|||||||
def register_adapter(self, config: AdapterRequiredConfig) -> None:
|
def register_adapter(self, config: AdapterRequiredConfig) -> None:
|
||||||
adapter_name = config.credentials.type
|
adapter_name = config.credentials.type
|
||||||
adapter_type = self.get_adapter_class_by_name(adapter_name)
|
adapter_type = self.get_adapter_class_by_name(adapter_name)
|
||||||
|
adapter_version = import_module(f".{adapter_name}.__version__", "dbt.adapters").version
|
||||||
|
adapter_version_specifier = VersionSpecifier.from_version_string(
|
||||||
|
adapter_version
|
||||||
|
).to_version_string()
|
||||||
|
fire_event(
|
||||||
|
AdapterRegistered(adapter_name=adapter_name, adapter_version=adapter_version_specifier)
|
||||||
|
)
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if adapter_name in self.adapters:
|
if adapter_name in self.adapters:
|
||||||
# this shouldn't really happen...
|
# this shouldn't really happen...
|
||||||
@@ -132,7 +139,7 @@ class AdapterContainer:
|
|||||||
try:
|
try:
|
||||||
plugin = self.plugins[plugin_name]
|
plugin = self.plugins[plugin_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InternalException(f"No plugin found for {plugin_name}") from None
|
raise DbtInternalError(f"No plugin found for {plugin_name}") from None
|
||||||
plugins.append(plugin)
|
plugins.append(plugin)
|
||||||
seen.add(plugin_name)
|
seen.add(plugin_name)
|
||||||
for dep in plugin.dependencies:
|
for dep in plugin.dependencies:
|
||||||
@@ -151,13 +158,16 @@ class AdapterContainer:
|
|||||||
try:
|
try:
|
||||||
path = self.packages[package_name]
|
path = self.packages[package_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InternalException(f"No internal package listing found for {package_name}")
|
raise DbtInternalError(f"No internal package listing found for {package_name}")
|
||||||
paths.append(path)
|
paths.append(path)
|
||||||
return paths
|
return paths
|
||||||
|
|
||||||
def get_adapter_type_names(self, name: Optional[str]) -> List[str]:
|
def get_adapter_type_names(self, name: Optional[str]) -> List[str]:
|
||||||
return [p.adapter.type() for p in self.get_adapter_plugins(name)]
|
return [p.adapter.type() for p in self.get_adapter_plugins(name)]
|
||||||
|
|
||||||
|
def get_adapter_constraint_support(self, name: Optional[str]) -> List[str]:
|
||||||
|
return self.lookup_adapter(name).CONSTRAINT_SUPPORT # type: ignore
|
||||||
|
|
||||||
|
|
||||||
FACTORY: AdapterContainer = AdapterContainer()
|
FACTORY: AdapterContainer = AdapterContainer()
|
||||||
|
|
||||||
@@ -214,6 +224,10 @@ def get_adapter_type_names(name: Optional[str]) -> List[str]:
|
|||||||
return FACTORY.get_adapter_type_names(name)
|
return FACTORY.get_adapter_type_names(name)
|
||||||
|
|
||||||
|
|
||||||
|
def get_adapter_constraint_support(name: Optional[str]) -> List[str]:
|
||||||
|
return FACTORY.get_adapter_constraint_support(name)
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def adapter_management():
|
def adapter_management():
|
||||||
reset_adapters()
|
reset_adapters()
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from typing import (
|
|||||||
Generic,
|
Generic,
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
|
||||||
Dict,
|
Dict,
|
||||||
Any,
|
Any,
|
||||||
)
|
)
|
||||||
@@ -17,8 +16,7 @@ from typing_extensions import Protocol
|
|||||||
import agate
|
import agate
|
||||||
|
|
||||||
from dbt.contracts.connection import Connection, AdapterRequiredConfig, AdapterResponse
|
from dbt.contracts.connection import Connection, AdapterRequiredConfig, AdapterResponse
|
||||||
from dbt.contracts.graph.compiled import CompiledNode, ManifestNode, NonSourceCompiledNode
|
from dbt.contracts.graph.nodes import ResultNode, ManifestNode
|
||||||
from dbt.contracts.graph.parsed import ParsedNode, ParsedSourceDefinition
|
|
||||||
from dbt.contracts.graph.model_config import BaseConfig
|
from dbt.contracts.graph.model_config import BaseConfig
|
||||||
from dbt.contracts.graph.manifest import Manifest
|
from dbt.contracts.graph.manifest import Manifest
|
||||||
from dbt.contracts.relation import Policy, HasQuoting
|
from dbt.contracts.relation import Policy, HasQuoting
|
||||||
@@ -48,11 +46,7 @@ class RelationProtocol(Protocol):
|
|||||||
...
|
...
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from(
|
def create_from(cls: Type[Self], config: HasQuoting, node: ResultNode) -> Self:
|
||||||
cls: Type[Self],
|
|
||||||
config: HasQuoting,
|
|
||||||
node: Union[CompiledNode, ParsedNode, ParsedSourceDefinition],
|
|
||||||
) -> Self:
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
@@ -65,7 +59,7 @@ class CompilerProtocol(Protocol):
|
|||||||
node: ManifestNode,
|
node: ManifestNode,
|
||||||
manifest: Manifest,
|
manifest: Manifest,
|
||||||
extra_context: Optional[Dict[str, Any]] = None,
|
extra_context: Optional[Dict[str, Any]] = None,
|
||||||
) -> NonSourceCompiledNode:
|
) -> ManifestNode:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from dbt.events.proto_types import ReferenceKeyMsg
|
|
||||||
|
|
||||||
|
|
||||||
_ReferenceKey = namedtuple("_ReferenceKey", "database schema identifier")
|
_ReferenceKey = namedtuple("_ReferenceKey", "database schema identifier")
|
||||||
@@ -30,11 +29,9 @@ def _make_ref_key(relation: Any) -> _ReferenceKey:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _make_ref_key_msg(relation: Any):
|
def _make_ref_key_dict(relation: Any):
|
||||||
return _make_msg_from_ref_key(_make_ref_key(relation))
|
return {
|
||||||
|
"database": relation.database,
|
||||||
|
"schema": relation.schema,
|
||||||
def _make_msg_from_ref_key(ref_key: _ReferenceKey) -> ReferenceKeyMsg:
|
"identifier": relation.identifier,
|
||||||
return ReferenceKeyMsg(
|
}
|
||||||
database=ref_key.database, schema=ref_key.schema, identifier=ref_key.identifier
|
|
||||||
)
|
|
||||||
|
|||||||
25
core/dbt/adapters/relation_configs/README.md
Normal file
25
core/dbt/adapters/relation_configs/README.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# RelationConfig
|
||||||
|
This package serves as an initial abstraction for managing the inspection of existing relations and determining
|
||||||
|
changes on those relations. It arose from the materialized view work and is currently only supporting
|
||||||
|
materialized views for Postgres and Redshift as well as dynamic tables for Snowflake. There are three main
|
||||||
|
classes in this package.
|
||||||
|
|
||||||
|
## RelationConfigBase
|
||||||
|
This is a very small class that only has a `from_dict()` method and a default `NotImplementedError()`. At some
|
||||||
|
point this could be replaced by a more robust framework, like `mashumaro` or `pydantic`.
|
||||||
|
|
||||||
|
## RelationConfigChange
|
||||||
|
This class inherits from `RelationConfigBase` ; however, this can be thought of as a separate class. The subclassing
|
||||||
|
merely points to the idea that both classes would likely inherit from the same class in a `mashumaro` or
|
||||||
|
`pydantic` implementation. This class is much more restricted in attribution. It should really only
|
||||||
|
ever need an `action` and a `context`. This can be though of as being analogous to a web request. You need to
|
||||||
|
know what you're doing (`action`: 'create' = GET, 'drop' = DELETE, etc.) and the information (`context`) needed
|
||||||
|
to make the change. In our scenarios, the context tends to be an instance of `RelationConfigBase` corresponding
|
||||||
|
to the new state.
|
||||||
|
|
||||||
|
## RelationConfigValidationMixin
|
||||||
|
This mixin provides optional validation mechanics that can be applied to either `RelationConfigBase` or
|
||||||
|
`RelationConfigChange` subclasses. A validation rule is a combination of a `validation_check`, something
|
||||||
|
that should evaluate to `True`, and an optional `validation_error`, an instance of `DbtRuntimeError`
|
||||||
|
that should be raised in the event the `validation_check` fails. While optional, it's recommended that
|
||||||
|
the `validation_error` be provided for clearer transparency to the end user.
|
||||||
12
core/dbt/adapters/relation_configs/__init__.py
Normal file
12
core/dbt/adapters/relation_configs/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from dbt.adapters.relation_configs.config_base import ( # noqa: F401
|
||||||
|
RelationConfigBase,
|
||||||
|
RelationResults,
|
||||||
|
)
|
||||||
|
from dbt.adapters.relation_configs.config_change import ( # noqa: F401
|
||||||
|
RelationConfigChangeAction,
|
||||||
|
RelationConfigChange,
|
||||||
|
)
|
||||||
|
from dbt.adapters.relation_configs.config_validation import ( # noqa: F401
|
||||||
|
RelationConfigValidationMixin,
|
||||||
|
RelationConfigValidationRule,
|
||||||
|
)
|
||||||
44
core/dbt/adapters/relation_configs/config_base.py
Normal file
44
core/dbt/adapters/relation_configs/config_base.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Union, Dict
|
||||||
|
|
||||||
|
import agate
|
||||||
|
from dbt.utils import filter_null_values
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is what relation metadata from the database looks like. It's a dictionary because there will be
|
||||||
|
multiple grains of data for a single object. For example, a materialized view in Postgres has base level information,
|
||||||
|
like name. But it also can have multiple indexes, which needs to be a separate query. It might look like this:
|
||||||
|
|
||||||
|
{
|
||||||
|
"base": agate.Row({"table_name": "table_abc", "query": "select * from table_def"})
|
||||||
|
"indexes": agate.Table("rows": [
|
||||||
|
agate.Row({"name": "index_a", "columns": ["column_a"], "type": "hash", "unique": False}),
|
||||||
|
agate.Row({"name": "index_b", "columns": ["time_dim_a"], "type": "btree", "unique": False}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
RelationResults = Dict[str, Union[agate.Row, agate.Table]]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RelationConfigBase:
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, kwargs_dict) -> "RelationConfigBase":
|
||||||
|
"""
|
||||||
|
This assumes the subclass of `RelationConfigBase` is flat, in the sense that no attribute is
|
||||||
|
itself another subclass of `RelationConfigBase`. If that's not the case, this should be overriden
|
||||||
|
to manually manage that complexity.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
kwargs_dict: the dict representation of this instance
|
||||||
|
|
||||||
|
Returns: the `RelationConfigBase` representation associated with the provided dict
|
||||||
|
"""
|
||||||
|
return cls(**filter_null_values(kwargs_dict)) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _not_implemented_error(cls) -> NotImplementedError:
|
||||||
|
return NotImplementedError(
|
||||||
|
"This relation type has not been fully configured for this adapter."
|
||||||
|
)
|
||||||
23
core/dbt/adapters/relation_configs/config_change.py
Normal file
23
core/dbt/adapters/relation_configs/config_change.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Hashable
|
||||||
|
|
||||||
|
from dbt.adapters.relation_configs.config_base import RelationConfigBase
|
||||||
|
from dbt.dataclass_schema import StrEnum
|
||||||
|
|
||||||
|
|
||||||
|
class RelationConfigChangeAction(StrEnum):
|
||||||
|
alter = "alter"
|
||||||
|
create = "create"
|
||||||
|
drop = "drop"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, eq=True, unsafe_hash=True)
|
||||||
|
class RelationConfigChange(RelationConfigBase, ABC):
|
||||||
|
action: RelationConfigChangeAction
|
||||||
|
context: Hashable # this is usually a RelationConfig, e.g. IndexConfig, but shouldn't be limited
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def requires_full_refresh(self) -> bool:
|
||||||
|
raise self._not_implemented_error()
|
||||||
57
core/dbt/adapters/relation_configs/config_validation.py
Normal file
57
core/dbt/adapters/relation_configs/config_validation.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Set, Optional
|
||||||
|
|
||||||
|
from dbt.exceptions import DbtRuntimeError
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, eq=True, unsafe_hash=True)
|
||||||
|
class RelationConfigValidationRule:
|
||||||
|
validation_check: bool
|
||||||
|
validation_error: Optional[DbtRuntimeError]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_error(self):
|
||||||
|
return DbtRuntimeError(
|
||||||
|
"There was a validation error in preparing this relation config."
|
||||||
|
"No additional context was provided by this adapter."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class RelationConfigValidationMixin:
|
||||||
|
def __post_init__(self):
|
||||||
|
self.run_validation_rules()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def validation_rules(self) -> Set[RelationConfigValidationRule]:
|
||||||
|
"""
|
||||||
|
A set of validation rules to run against the object upon creation.
|
||||||
|
|
||||||
|
A validation rule is a combination of a validation check (bool) and an optional error message.
|
||||||
|
|
||||||
|
This defaults to no validation rules if not implemented. It's recommended to override this with values,
|
||||||
|
but that may not always be necessary.
|
||||||
|
|
||||||
|
Returns: a set of validation rules
|
||||||
|
"""
|
||||||
|
return set()
|
||||||
|
|
||||||
|
def run_validation_rules(self):
|
||||||
|
for validation_rule in self.validation_rules:
|
||||||
|
try:
|
||||||
|
assert validation_rule.validation_check
|
||||||
|
except AssertionError:
|
||||||
|
if validation_rule.validation_error:
|
||||||
|
raise validation_rule.validation_error
|
||||||
|
else:
|
||||||
|
raise validation_rule.default_error
|
||||||
|
self.run_child_validation_rules()
|
||||||
|
|
||||||
|
def run_child_validation_rules(self):
|
||||||
|
for attr_value in vars(self).values():
|
||||||
|
if hasattr(attr_value, "validation_rules"):
|
||||||
|
attr_value.run_validation_rules()
|
||||||
|
if isinstance(attr_value, set):
|
||||||
|
for member in attr_value:
|
||||||
|
if hasattr(member, "validation_rules"):
|
||||||
|
member.run_validation_rules()
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import abc
|
import abc
|
||||||
import time
|
import time
|
||||||
from typing import List, Optional, Tuple, Any, Iterable, Dict
|
from typing import List, Optional, Tuple, Any, Iterable, Dict, Union
|
||||||
|
|
||||||
import agate
|
import agate
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ from dbt.adapters.base import BaseConnectionManager
|
|||||||
from dbt.contracts.connection import Connection, ConnectionState, AdapterResponse
|
from dbt.contracts.connection import Connection, ConnectionState, AdapterResponse
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import ConnectionUsed, SQLQuery, SQLCommit, SQLQueryStatus
|
from dbt.events.types import ConnectionUsed, SQLQuery, SQLCommit, SQLQueryStatus
|
||||||
|
from dbt.events.contextvars import get_node_info
|
||||||
from dbt.utils import cast_to_str
|
from dbt.utils import cast_to_str
|
||||||
|
|
||||||
|
|
||||||
@@ -26,9 +27,7 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def cancel(self, connection: Connection):
|
def cancel(self, connection: Connection):
|
||||||
"""Cancel the given connection."""
|
"""Cancel the given connection."""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError("`cancel` is not implemented for this adapter!")
|
||||||
"`cancel` is not implemented for this adapter!"
|
|
||||||
)
|
|
||||||
|
|
||||||
def cancel_open(self) -> List[str]:
|
def cancel_open(self) -> List[str]:
|
||||||
names = []
|
names = []
|
||||||
@@ -56,7 +55,13 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
connection = self.get_thread_connection()
|
connection = self.get_thread_connection()
|
||||||
if auto_begin and connection.transaction_open is False:
|
if auto_begin and connection.transaction_open is False:
|
||||||
self.begin()
|
self.begin()
|
||||||
fire_event(ConnectionUsed(conn_type=self.TYPE, conn_name=cast_to_str(connection.name)))
|
fire_event(
|
||||||
|
ConnectionUsed(
|
||||||
|
conn_type=self.TYPE,
|
||||||
|
conn_name=cast_to_str(connection.name),
|
||||||
|
node_info=get_node_info(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
with self.exception_handler(sql):
|
with self.exception_handler(sql):
|
||||||
if abridge_sql_log:
|
if abridge_sql_log:
|
||||||
@@ -64,7 +69,11 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
else:
|
else:
|
||||||
log_sql = sql
|
log_sql = sql
|
||||||
|
|
||||||
fire_event(SQLQuery(conn_name=cast_to_str(connection.name), sql=log_sql))
|
fire_event(
|
||||||
|
SQLQuery(
|
||||||
|
conn_name=cast_to_str(connection.name), sql=log_sql, node_info=get_node_info()
|
||||||
|
)
|
||||||
|
)
|
||||||
pre = time.time()
|
pre = time.time()
|
||||||
|
|
||||||
cursor = connection.handle.cursor()
|
cursor = connection.handle.cursor()
|
||||||
@@ -72,7 +81,9 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
|
|
||||||
fire_event(
|
fire_event(
|
||||||
SQLQueryStatus(
|
SQLQueryStatus(
|
||||||
status=str(self.get_response(cursor)), elapsed=round((time.time() - pre), 2)
|
status=str(self.get_response(cursor)),
|
||||||
|
elapsed=round((time.time() - pre)),
|
||||||
|
node_info=get_node_info(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -82,7 +93,7 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_response(cls, cursor: Any) -> AdapterResponse:
|
def get_response(cls, cursor: Any) -> AdapterResponse:
|
||||||
"""Get the status of the cursor."""
|
"""Get the status of the cursor."""
|
||||||
raise dbt.exceptions.NotImplementedException(
|
raise dbt.exceptions.NotImplementedError(
|
||||||
"`get_response` is not implemented for this adapter!"
|
"`get_response` is not implemented for this adapter!"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -106,25 +117,36 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
return [dict(zip(column_names, row)) for row in rows]
|
return [dict(zip(column_names, row)) for row in rows]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_result_from_cursor(cls, cursor: Any) -> agate.Table:
|
def get_result_from_cursor(cls, cursor: Any, limit: Optional[int]) -> agate.Table:
|
||||||
data: List[Any] = []
|
data: List[Any] = []
|
||||||
column_names: List[str] = []
|
column_names: List[str] = []
|
||||||
|
|
||||||
if cursor.description is not None:
|
if cursor.description is not None:
|
||||||
column_names = [col[0] for col in cursor.description]
|
column_names = [col[0] for col in cursor.description]
|
||||||
rows = cursor.fetchall()
|
if limit:
|
||||||
|
rows = cursor.fetchmany(limit)
|
||||||
|
else:
|
||||||
|
rows = cursor.fetchall()
|
||||||
data = cls.process_results(column_names, rows)
|
data = cls.process_results(column_names, rows)
|
||||||
|
|
||||||
return dbt.clients.agate_helper.table_from_data_flat(data, column_names)
|
return dbt.clients.agate_helper.table_from_data_flat(data, column_names)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def data_type_code_to_name(cls, type_code: Union[int, str]) -> str:
|
||||||
|
"""Get the string representation of the data type from the type_code."""
|
||||||
|
# https://peps.python.org/pep-0249/#type-objects
|
||||||
|
raise dbt.exceptions.NotImplementedError(
|
||||||
|
"`data_type_code_to_name` is not implemented for this adapter!"
|
||||||
|
)
|
||||||
|
|
||||||
def execute(
|
def execute(
|
||||||
self, sql: str, auto_begin: bool = False, fetch: bool = False
|
self, sql: str, auto_begin: bool = False, fetch: bool = False, limit: Optional[int] = None
|
||||||
) -> Tuple[AdapterResponse, agate.Table]:
|
) -> Tuple[AdapterResponse, agate.Table]:
|
||||||
sql = self._add_query_comment(sql)
|
sql = self._add_query_comment(sql)
|
||||||
_, cursor = self.add_query(sql, auto_begin)
|
_, cursor = self.add_query(sql, auto_begin)
|
||||||
response = self.get_response(cursor)
|
response = self.get_response(cursor)
|
||||||
if fetch:
|
if fetch:
|
||||||
table = self.get_result_from_cursor(cursor)
|
table = self.get_result_from_cursor(cursor, limit)
|
||||||
else:
|
else:
|
||||||
table = dbt.clients.agate_helper.empty_table()
|
table = dbt.clients.agate_helper.empty_table()
|
||||||
return response, table
|
return response, table
|
||||||
@@ -135,10 +157,14 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
def add_commit_query(self):
|
def add_commit_query(self):
|
||||||
return self.add_query("COMMIT", auto_begin=False)
|
return self.add_query("COMMIT", auto_begin=False)
|
||||||
|
|
||||||
|
def add_select_query(self, sql: str) -> Tuple[Connection, Any]:
|
||||||
|
sql = self._add_query_comment(sql)
|
||||||
|
return self.add_query(sql, auto_begin=False)
|
||||||
|
|
||||||
def begin(self):
|
def begin(self):
|
||||||
connection = self.get_thread_connection()
|
connection = self.get_thread_connection()
|
||||||
if connection.transaction_open is True:
|
if connection.transaction_open is True:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
'Tried to begin a new transaction on connection "{}", but '
|
'Tried to begin a new transaction on connection "{}", but '
|
||||||
"it already had one open!".format(connection.name)
|
"it already had one open!".format(connection.name)
|
||||||
)
|
)
|
||||||
@@ -151,12 +177,12 @@ class SQLConnectionManager(BaseConnectionManager):
|
|||||||
def commit(self):
|
def commit(self):
|
||||||
connection = self.get_thread_connection()
|
connection = self.get_thread_connection()
|
||||||
if connection.transaction_open is False:
|
if connection.transaction_open is False:
|
||||||
raise dbt.exceptions.InternalException(
|
raise dbt.exceptions.DbtInternalError(
|
||||||
'Tried to commit transaction on connection "{}", but '
|
'Tried to commit transaction on connection "{}", but '
|
||||||
"it does not have one open!".format(connection.name)
|
"it does not have one open!".format(connection.name)
|
||||||
)
|
)
|
||||||
|
|
||||||
fire_event(SQLCommit(conn_name=connection.name))
|
fire_event(SQLCommit(conn_name=connection.name, node_info=get_node_info()))
|
||||||
self.add_commit_query()
|
self.add_commit_query()
|
||||||
|
|
||||||
connection.transaction_open = False
|
connection.transaction_open = False
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import agate
|
import agate
|
||||||
from typing import Any, Optional, Tuple, Type, List
|
from typing import Any, Optional, Tuple, Type, List
|
||||||
|
|
||||||
import dbt.clients.agate_helper
|
from dbt.contracts.connection import Connection, AdapterResponse
|
||||||
from dbt.contracts.connection import Connection
|
from dbt.exceptions import RelationTypeNullError
|
||||||
import dbt.exceptions
|
|
||||||
from dbt.adapters.base import BaseAdapter, available
|
from dbt.adapters.base import BaseAdapter, available
|
||||||
from dbt.adapters.cache import _make_ref_key_msg
|
from dbt.adapters.cache import _make_ref_key_dict
|
||||||
from dbt.adapters.sql import SQLConnectionManager
|
from dbt.adapters.sql import SQLConnectionManager
|
||||||
from dbt.events.functions import fire_event
|
from dbt.events.functions import fire_event
|
||||||
from dbt.events.types import ColTypeChange, SchemaCreation, SchemaDrop
|
from dbt.events.types import ColTypeChange, SchemaCreation, SchemaDrop
|
||||||
@@ -23,6 +22,7 @@ RENAME_RELATION_MACRO_NAME = "rename_relation"
|
|||||||
TRUNCATE_RELATION_MACRO_NAME = "truncate_relation"
|
TRUNCATE_RELATION_MACRO_NAME = "truncate_relation"
|
||||||
DROP_RELATION_MACRO_NAME = "drop_relation"
|
DROP_RELATION_MACRO_NAME = "drop_relation"
|
||||||
ALTER_COLUMN_TYPE_MACRO_NAME = "alter_column_type"
|
ALTER_COLUMN_TYPE_MACRO_NAME = "alter_column_type"
|
||||||
|
VALIDATE_SQL_MACRO_NAME = "validate_sql"
|
||||||
|
|
||||||
|
|
||||||
class SQLAdapter(BaseAdapter):
|
class SQLAdapter(BaseAdapter):
|
||||||
@@ -110,7 +110,7 @@ class SQLAdapter(BaseAdapter):
|
|||||||
ColTypeChange(
|
ColTypeChange(
|
||||||
orig_type=target_column.data_type,
|
orig_type=target_column.data_type,
|
||||||
new_type=new_type,
|
new_type=new_type,
|
||||||
table=_make_ref_key_msg(current),
|
table=_make_ref_key_dict(current),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -132,9 +132,7 @@ class SQLAdapter(BaseAdapter):
|
|||||||
|
|
||||||
def drop_relation(self, relation):
|
def drop_relation(self, relation):
|
||||||
if relation.type is None:
|
if relation.type is None:
|
||||||
dbt.exceptions.raise_compiler_error(
|
raise RelationTypeNullError(relation)
|
||||||
"Tried to drop relation {}, but its type is null.".format(relation)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.cache_dropped(relation)
|
self.cache_dropped(relation)
|
||||||
self.execute_macro(DROP_RELATION_MACRO_NAME, kwargs={"relation": relation})
|
self.execute_macro(DROP_RELATION_MACRO_NAME, kwargs={"relation": relation})
|
||||||
@@ -155,7 +153,7 @@ class SQLAdapter(BaseAdapter):
|
|||||||
|
|
||||||
def create_schema(self, relation: BaseRelation) -> None:
|
def create_schema(self, relation: BaseRelation) -> None:
|
||||||
relation = relation.without_identifier()
|
relation = relation.without_identifier()
|
||||||
fire_event(SchemaCreation(relation=_make_ref_key_msg(relation)))
|
fire_event(SchemaCreation(relation=_make_ref_key_dict(relation)))
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"relation": relation,
|
"relation": relation,
|
||||||
}
|
}
|
||||||
@@ -166,7 +164,7 @@ class SQLAdapter(BaseAdapter):
|
|||||||
|
|
||||||
def drop_schema(self, relation: BaseRelation) -> None:
|
def drop_schema(self, relation: BaseRelation) -> None:
|
||||||
relation = relation.without_identifier()
|
relation = relation.without_identifier()
|
||||||
fire_event(SchemaDrop(relation=_make_ref_key_msg(relation)))
|
fire_event(SchemaDrop(relation=_make_ref_key_dict(relation)))
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"relation": relation,
|
"relation": relation,
|
||||||
}
|
}
|
||||||
@@ -200,6 +198,7 @@ class SQLAdapter(BaseAdapter):
|
|||||||
)
|
)
|
||||||
return relations
|
return relations
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def quote(self, identifier):
|
def quote(self, identifier):
|
||||||
return '"{}"'.format(identifier)
|
return '"{}"'.format(identifier)
|
||||||
|
|
||||||
@@ -220,6 +219,34 @@ class SQLAdapter(BaseAdapter):
|
|||||||
results = self.execute_macro(CHECK_SCHEMA_EXISTS_MACRO_NAME, kwargs=kwargs)
|
results = self.execute_macro(CHECK_SCHEMA_EXISTS_MACRO_NAME, kwargs=kwargs)
|
||||||
return results[0][0] > 0
|
return results[0][0] > 0
|
||||||
|
|
||||||
|
def validate_sql(self, sql: str) -> AdapterResponse:
|
||||||
|
"""Submit the given SQL to the engine for validation, but not execution.
|
||||||
|
|
||||||
|
By default we simply prefix the query with the explain keyword and allow the
|
||||||
|
exceptions thrown by the underlying engine on invalid SQL inputs to bubble up
|
||||||
|
to the exception handler. For adjustments to the explain statement - such as
|
||||||
|
for adapters that have different mechanisms for hinting at query validation
|
||||||
|
or dry-run - callers may be able to override the validate_sql_query macro with
|
||||||
|
the addition of an <adapter>__validate_sql implementation.
|
||||||
|
|
||||||
|
:param sql str: The sql to validate
|
||||||
|
"""
|
||||||
|
kwargs = {
|
||||||
|
"sql": sql,
|
||||||
|
}
|
||||||
|
result = self.execute_macro(VALIDATE_SQL_MACRO_NAME, kwargs=kwargs)
|
||||||
|
# The statement macro always returns an AdapterResponse in the output AttrDict's
|
||||||
|
# `response` property, and we preserve the full payload in case we want to
|
||||||
|
# return fetched output for engines where explain plans are emitted as columnar
|
||||||
|
# results. Any macro override that deviates from this behavior may encounter an
|
||||||
|
# assertion error in the runtime.
|
||||||
|
adapter_response = result.response # type: ignore[attr-defined]
|
||||||
|
assert isinstance(adapter_response, AdapterResponse), (
|
||||||
|
f"Expected AdapterResponse from validate_sql macro execution, "
|
||||||
|
f"got {type(adapter_response)}."
|
||||||
|
)
|
||||||
|
return adapter_response
|
||||||
|
|
||||||
# This is for use in the test suite
|
# This is for use in the test suite
|
||||||
def run_sql_for_tests(self, sql, fetch, conn):
|
def run_sql_for_tests(self, sql, fetch, conn):
|
||||||
cursor = conn.handle.cursor()
|
cursor = conn.handle.cursor()
|
||||||
|
|||||||
@@ -1 +1,71 @@
|
|||||||
TODO
|
# Adding a new command
|
||||||
|
|
||||||
|
## `main.py`
|
||||||
|
Add the new command with all necessary decorators. Every command will need at minimum:
|
||||||
|
- a decorator for the click group it belongs to which also names the command
|
||||||
|
- the postflight decorator (must come before other decorators from the `requires` module for error handling)
|
||||||
|
- the preflight decorator
|
||||||
|
```py
|
||||||
|
@cli.command("my-new-command")
|
||||||
|
@requires.postflight
|
||||||
|
@requires.preflight
|
||||||
|
def my_new_command(ctx, **kwargs):
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## `types.py`
|
||||||
|
Add an entry to the `Command` enum with your new command. Commands that are sub-commands should have entries
|
||||||
|
that represent their full command path (e.g. `source freshness -> SOURCE_FRESHNESS`, `docs serve -> DOCS_SERVE`).
|
||||||
|
|
||||||
|
## `flags.py`
|
||||||
|
Add the new command to the dictionary within the `command_args` function.
|
||||||
|
|
||||||
|
# Exception Handling
|
||||||
|
|
||||||
|
## `requires.py`
|
||||||
|
|
||||||
|
### `postflight`
|
||||||
|
In the postflight decorator, the click command is invoked (i.e. `func(*args, **kwargs)`) and wrapped in a `try/except` block to handle any exceptions thrown.
|
||||||
|
Any exceptions thrown from `postflight` are wrapped by custom exceptions from the `dbt.cli.exceptions` module (i.e. `ResultExit`, `ExceptionExit`) to instruct click to complete execution with a particular exit code.
|
||||||
|
|
||||||
|
Some `dbt-core` handled exceptions have an attribute named `results` which contains results from running nodes (e.g. `FailFastError`). These are wrapped in the `ResultExit` exception to represent runs that have failed in a way that `dbt-core` expects.
|
||||||
|
If the invocation of the command does not throw any exceptions but does not succeed, `postflight` will still raise the `ResultExit` exception to make use of the exit code.
|
||||||
|
These exceptions produce an exit code of `1`.
|
||||||
|
|
||||||
|
Exceptions wrapped with `ExceptionExit` may be thrown by `dbt-core` intentionally (i.e. an exception that inherits from `dbt.exceptions.Exception`) or unintentionally (i.e. exceptions thrown by the python runtime). In either case these are considered errors that `dbt-core` did not expect and are treated as genuine exceptions.
|
||||||
|
These exceptions produce an exit code of `2`.
|
||||||
|
|
||||||
|
If no exceptions are thrown from invoking the command and the command succeeds, `postflight` will not raise any exceptions.
|
||||||
|
When no exceptions are raised an exit code of `0` is produced.
|
||||||
|
|
||||||
|
## `main.py`
|
||||||
|
|
||||||
|
### `dbtRunner`
|
||||||
|
`dbtRunner` provides a programmatic interface for our click CLI and wraps the invocation of the click commands to handle any exceptions thrown.
|
||||||
|
|
||||||
|
`dbtRunner.invoke` should ideally only ever return an instantiated `dbtRunnerResult` which contains the following fields:
|
||||||
|
- `success`: A boolean representing whether the command invocation was successful
|
||||||
|
- `result`: The optional result of the command invoked. This attribute can have many types, please see the definition of `dbtRunnerResult` for more information
|
||||||
|
- `exception`: If an exception was thrown during command invocation it will be saved here, otherwise it will be `None`. Please note that the exceptions held in this attribute are not the exceptions thrown by `preflight` but instead the exceptions that `ResultExit` and `ExceptionExit` wrap
|
||||||
|
|
||||||
|
Programmatic exception handling might look like the following:
|
||||||
|
```python
|
||||||
|
res = dbtRunner().invoke(["run"])
|
||||||
|
if not res.success:
|
||||||
|
...
|
||||||
|
if type(res.exception) == SomeExceptionType:
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## `dbt/tests/util.py`
|
||||||
|
|
||||||
|
### `run_dbt`
|
||||||
|
In many of our functional and integration tests, we want to be sure that an invocation of `dbt` raises a certain exception.
|
||||||
|
A common pattern for these assertions:
|
||||||
|
```python
|
||||||
|
class TestSomething:
|
||||||
|
def test_something(self, project):
|
||||||
|
with pytest.raises(SomeException):
|
||||||
|
run_dbt(["run"])
|
||||||
|
```
|
||||||
|
To allow these tests to assert that exceptions have been thrown, the `run_dbt` function will raise any exceptions it recieves from the invocation of a `dbt` command.
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from .main import cli as dbt_cli # noqa
|
||||||
|
|||||||
16
core/dbt/cli/context.py
Normal file
16
core/dbt/cli/context.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import click
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from dbt.cli.main import cli as dbt
|
||||||
|
|
||||||
|
|
||||||
|
def make_context(args, command=dbt) -> Optional[click.Context]:
|
||||||
|
try:
|
||||||
|
ctx = command.make_context(command.name, args)
|
||||||
|
except click.exceptions.Exit:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ctx.invoked_subcommand = ctx.protected_args[0] if ctx.protected_args else None
|
||||||
|
ctx.obj = {}
|
||||||
|
|
||||||
|
return ctx
|
||||||
43
core/dbt/cli/exceptions.py
Normal file
43
core/dbt/cli/exceptions.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from typing import Optional, IO
|
||||||
|
|
||||||
|
from click.exceptions import ClickException
|
||||||
|
from dbt.utils import ExitCodes
|
||||||
|
|
||||||
|
|
||||||
|
class DbtUsageException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DbtInternalException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CliException(ClickException):
|
||||||
|
"""The base exception class for our implementation of the click CLI.
|
||||||
|
The exit_code attribute is used by click to determine which exit code to produce
|
||||||
|
after an invocation."""
|
||||||
|
|
||||||
|
def __init__(self, exit_code: ExitCodes) -> None:
|
||||||
|
self.exit_code = exit_code.value
|
||||||
|
|
||||||
|
# the typing of _file is to satisfy the signature of ClickException.show
|
||||||
|
# overriding this method prevents click from printing any exceptions to stdout
|
||||||
|
def show(self, _file: Optional[IO] = None) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ResultExit(CliException):
|
||||||
|
"""This class wraps any exception that contains results while invoking dbt, or the
|
||||||
|
results of an invocation that did not succeed but did not throw any exceptions."""
|
||||||
|
|
||||||
|
def __init__(self, result) -> None:
|
||||||
|
super().__init__(ExitCodes.ModelError)
|
||||||
|
self.result = result
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionExit(CliException):
|
||||||
|
"""This class wraps any exception that does not contain results thrown while invoking dbt."""
|
||||||
|
|
||||||
|
def __init__(self, exception: Exception) -> None:
|
||||||
|
super().__init__(ExitCodes.UnhandledError)
|
||||||
|
self.exception = exception
|
||||||
@@ -1,44 +1,404 @@
|
|||||||
# TODO Move this to /core/dbt/flags.py when we're ready to break things
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from importlib import import_module
|
||||||
from multiprocessing import get_context
|
from multiprocessing import get_context
|
||||||
from pprint import pformat as pf
|
from pprint import pformat as pf
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
from click import get_current_context
|
from click import Context, get_current_context, Parameter
|
||||||
|
from click.core import Command as ClickCommand, Group, ParameterSource
|
||||||
|
from dbt.cli.exceptions import DbtUsageException
|
||||||
|
from dbt.cli.resolvers import default_log_path, default_project_dir
|
||||||
|
from dbt.cli.types import Command as CliCommand
|
||||||
|
from dbt.config.profile import read_user_config
|
||||||
|
from dbt.contracts.project import UserConfig
|
||||||
|
from dbt.exceptions import DbtInternalError
|
||||||
|
from dbt.deprecations import renamed_env_var
|
||||||
|
from dbt.helper_types import WarnErrorOptions
|
||||||
|
|
||||||
if os.name != "nt":
|
if os.name != "nt":
|
||||||
# https://bugs.python.org/issue41567
|
# https://bugs.python.org/issue41567
|
||||||
import multiprocessing.popen_spawn_posix # type: ignore # noqa: F401
|
import multiprocessing.popen_spawn_posix # type: ignore # noqa: F401
|
||||||
|
|
||||||
|
FLAGS_DEFAULTS = {
|
||||||
|
"INDIRECT_SELECTION": "eager",
|
||||||
|
"TARGET_PATH": None,
|
||||||
|
# Cli args without user_config or env var option.
|
||||||
|
"FULL_REFRESH": False,
|
||||||
|
"STRICT_MODE": False,
|
||||||
|
"STORE_FAILURES": False,
|
||||||
|
"INTROSPECT": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
DEPRECATED_PARAMS = {
|
||||||
|
"deprecated_defer": "defer",
|
||||||
|
"deprecated_favor_state": "favor_state",
|
||||||
|
"deprecated_print": "print",
|
||||||
|
"deprecated_state": "state",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
WHICH_KEY = "which"
|
||||||
|
|
||||||
|
|
||||||
|
def convert_config(config_name, config_value):
|
||||||
|
"""Convert the values from config and original set_from_args to the correct type."""
|
||||||
|
ret = config_value
|
||||||
|
if config_name.lower() == "warn_error_options" and type(config_value) == dict:
|
||||||
|
ret = WarnErrorOptions(
|
||||||
|
include=config_value.get("include", []), exclude=config_value.get("exclude", [])
|
||||||
|
)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def args_to_context(args: List[str]) -> Context:
|
||||||
|
"""Convert a list of args to a click context with proper hierarchy for dbt commands"""
|
||||||
|
from dbt.cli.main import cli
|
||||||
|
|
||||||
|
cli_ctx = cli.make_context(cli.name, args)
|
||||||
|
# Split args if they're a comma seperated string.
|
||||||
|
if len(args) == 1 and "," in args[0]:
|
||||||
|
args = args[0].split(",")
|
||||||
|
sub_command_name, sub_command, args = cli.resolve_command(cli_ctx, args)
|
||||||
|
|
||||||
|
# Handle source and docs group.
|
||||||
|
if isinstance(sub_command, Group):
|
||||||
|
sub_command_name, sub_command, args = sub_command.resolve_command(cli_ctx, args)
|
||||||
|
|
||||||
|
assert isinstance(sub_command, ClickCommand)
|
||||||
|
sub_command_ctx = sub_command.make_context(sub_command_name, args)
|
||||||
|
sub_command_ctx.parent = cli_ctx
|
||||||
|
return sub_command_ctx
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Flags:
|
class Flags:
|
||||||
def __init__(self, ctx=None) -> None:
|
"""Primary configuration artifact for running dbt"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, ctx: Optional[Context] = None, user_config: Optional[UserConfig] = None
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
# Set the default flags.
|
||||||
|
for key, value in FLAGS_DEFAULTS.items():
|
||||||
|
object.__setattr__(self, key, value)
|
||||||
|
|
||||||
if ctx is None:
|
if ctx is None:
|
||||||
ctx = get_current_context()
|
ctx = get_current_context()
|
||||||
|
|
||||||
def assign_params(ctx):
|
def _get_params_by_source(ctx: Context, source_type: ParameterSource):
|
||||||
|
"""Generates all params of a given source type."""
|
||||||
|
yield from [
|
||||||
|
name for name, source in ctx._parameter_source.items() if source is source_type
|
||||||
|
]
|
||||||
|
if ctx.parent:
|
||||||
|
yield from _get_params_by_source(ctx.parent, source_type)
|
||||||
|
|
||||||
|
# Ensure that any params sourced from the commandline are not present more than once.
|
||||||
|
# Click handles this exclusivity, but only at a per-subcommand level.
|
||||||
|
seen_params = []
|
||||||
|
for param in _get_params_by_source(ctx, ParameterSource.COMMANDLINE):
|
||||||
|
if param in seen_params:
|
||||||
|
raise DbtUsageException(
|
||||||
|
f"{param.lower()} was provided both before and after the subcommand, it can only be set either before or after.",
|
||||||
|
)
|
||||||
|
seen_params.append(param)
|
||||||
|
|
||||||
|
def _assign_params(
|
||||||
|
ctx: Context,
|
||||||
|
params_assigned_from_default: set,
|
||||||
|
deprecated_env_vars: Dict[str, Callable],
|
||||||
|
):
|
||||||
"""Recursively adds all click params to flag object"""
|
"""Recursively adds all click params to flag object"""
|
||||||
for param_name, param_value in ctx.params.items():
|
for param_name, param_value in ctx.params.items():
|
||||||
# N.B. You have to use the base MRO method (object.__setattr__) to set attributes
|
# N.B. You have to use the base MRO method (object.__setattr__) to set attributes
|
||||||
# when using frozen dataclasses.
|
# when using frozen dataclasses.
|
||||||
# https://docs.python.org/3/library/dataclasses.html#frozen-instances
|
# https://docs.python.org/3/library/dataclasses.html#frozen-instances
|
||||||
if hasattr(self, param_name):
|
|
||||||
raise Exception(f"Duplicate flag names found in click command: {param_name}")
|
# Handle deprecated env vars while still respecting old values
|
||||||
object.__setattr__(self, param_name.upper(), param_value)
|
# e.g. DBT_NO_PRINT -> DBT_PRINT if DBT_NO_PRINT is set, it is
|
||||||
|
# respected over DBT_PRINT or --print.
|
||||||
|
new_name: Union[str, None] = None
|
||||||
|
if param_name in DEPRECATED_PARAMS:
|
||||||
|
|
||||||
|
# Deprecated env vars can only be set via env var.
|
||||||
|
# We use the deprecated option in click to serialize the value
|
||||||
|
# from the env var string.
|
||||||
|
param_source = ctx.get_parameter_source(param_name)
|
||||||
|
if param_source == ParameterSource.DEFAULT:
|
||||||
|
continue
|
||||||
|
elif param_source != ParameterSource.ENVIRONMENT:
|
||||||
|
raise DbtUsageException(
|
||||||
|
"Deprecated parameters can only be set via environment variables",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rename for clarity.
|
||||||
|
dep_name = param_name
|
||||||
|
new_name = DEPRECATED_PARAMS.get(dep_name)
|
||||||
|
try:
|
||||||
|
assert isinstance(new_name, str)
|
||||||
|
except AssertionError:
|
||||||
|
raise Exception(
|
||||||
|
f"No deprecated param name match in DEPRECATED_PARAMS from {dep_name} to {new_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Find param objects for their envvar name.
|
||||||
|
try:
|
||||||
|
dep_param = [x for x in ctx.command.params if x.name == dep_name][0]
|
||||||
|
new_param = [x for x in ctx.command.params if x.name == new_name][0]
|
||||||
|
except IndexError:
|
||||||
|
raise Exception(
|
||||||
|
f"No deprecated param name match in context from {dep_name} to {new_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove param from defaulted set since the deprecated
|
||||||
|
# value is not set from default, but from an env var.
|
||||||
|
if new_name in params_assigned_from_default:
|
||||||
|
params_assigned_from_default.remove(new_name)
|
||||||
|
|
||||||
|
# Add the deprecation warning function to the set.
|
||||||
|
assert isinstance(dep_param.envvar, str)
|
||||||
|
assert isinstance(new_param.envvar, str)
|
||||||
|
deprecated_env_vars[new_name] = renamed_env_var(
|
||||||
|
old_name=dep_param.envvar,
|
||||||
|
new_name=new_param.envvar,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set the flag value.
|
||||||
|
is_duplicate = hasattr(self, param_name.upper())
|
||||||
|
is_default = ctx.get_parameter_source(param_name) == ParameterSource.DEFAULT
|
||||||
|
flag_name = (new_name or param_name).upper()
|
||||||
|
|
||||||
|
if (is_duplicate and not is_default) or not is_duplicate:
|
||||||
|
object.__setattr__(self, flag_name, param_value)
|
||||||
|
|
||||||
|
# Track default assigned params.
|
||||||
|
if is_default:
|
||||||
|
params_assigned_from_default.add(param_name)
|
||||||
|
|
||||||
if ctx.parent:
|
if ctx.parent:
|
||||||
assign_params(ctx.parent)
|
_assign_params(ctx.parent, params_assigned_from_default, deprecated_env_vars)
|
||||||
|
|
||||||
assign_params(ctx)
|
params_assigned_from_default = set() # type: Set[str]
|
||||||
|
deprecated_env_vars: Dict[str, Callable] = {}
|
||||||
|
_assign_params(ctx, params_assigned_from_default, deprecated_env_vars)
|
||||||
|
|
||||||
# Hard coded flags
|
# Set deprecated_env_var_warnings to be fired later after events have been init.
|
||||||
object.__setattr__(self, "WHICH", ctx.info_name)
|
object.__setattr__(
|
||||||
|
self, "deprecated_env_var_warnings", [x for x in deprecated_env_vars.values()]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the invoked command flags.
|
||||||
|
invoked_subcommand_name = (
|
||||||
|
ctx.invoked_subcommand if hasattr(ctx, "invoked_subcommand") else None
|
||||||
|
)
|
||||||
|
if invoked_subcommand_name is not None:
|
||||||
|
invoked_subcommand = getattr(import_module("dbt.cli.main"), invoked_subcommand_name)
|
||||||
|
invoked_subcommand.allow_extra_args = True
|
||||||
|
invoked_subcommand.ignore_unknown_options = True
|
||||||
|
invoked_subcommand_ctx = invoked_subcommand.make_context(None, sys.argv)
|
||||||
|
_assign_params(
|
||||||
|
invoked_subcommand_ctx, params_assigned_from_default, deprecated_env_vars
|
||||||
|
)
|
||||||
|
|
||||||
|
if not user_config:
|
||||||
|
profiles_dir = getattr(self, "PROFILES_DIR", None)
|
||||||
|
user_config = read_user_config(profiles_dir) if profiles_dir else None
|
||||||
|
|
||||||
|
# Add entire invocation command to flags
|
||||||
|
object.__setattr__(self, "INVOCATION_COMMAND", "dbt " + " ".join(sys.argv[1:]))
|
||||||
|
|
||||||
|
# Overwrite default assignments with user config if available.
|
||||||
|
if user_config:
|
||||||
|
param_assigned_from_default_copy = params_assigned_from_default.copy()
|
||||||
|
for param_assigned_from_default in params_assigned_from_default:
|
||||||
|
user_config_param_value = getattr(user_config, param_assigned_from_default, None)
|
||||||
|
if user_config_param_value is not None:
|
||||||
|
object.__setattr__(
|
||||||
|
self,
|
||||||
|
param_assigned_from_default.upper(),
|
||||||
|
convert_config(param_assigned_from_default, user_config_param_value),
|
||||||
|
)
|
||||||
|
param_assigned_from_default_copy.remove(param_assigned_from_default)
|
||||||
|
params_assigned_from_default = param_assigned_from_default_copy
|
||||||
|
|
||||||
|
# Set hard coded flags.
|
||||||
|
object.__setattr__(self, "WHICH", invoked_subcommand_name or ctx.info_name)
|
||||||
object.__setattr__(self, "MP_CONTEXT", get_context("spawn"))
|
object.__setattr__(self, "MP_CONTEXT", get_context("spawn"))
|
||||||
|
|
||||||
# Support console DO NOT TRACK initiave
|
# Apply the lead/follow relationship between some parameters.
|
||||||
if os.getenv("DO_NOT_TRACK", "").lower() in (1, "t", "true", "y", "yes"):
|
self._override_if_set("USE_COLORS", "USE_COLORS_FILE", params_assigned_from_default)
|
||||||
object.__setattr__(self, "ANONYMOUS_USAGE_STATS", False)
|
self._override_if_set("LOG_LEVEL", "LOG_LEVEL_FILE", params_assigned_from_default)
|
||||||
|
self._override_if_set("LOG_FORMAT", "LOG_FORMAT_FILE", params_assigned_from_default)
|
||||||
|
|
||||||
|
# Set default LOG_PATH from PROJECT_DIR, if available.
|
||||||
|
# Starting in v1.5, if `log-path` is set in `dbt_project.yml`, it will raise a deprecation warning,
|
||||||
|
# with the possibility of removing it in a future release.
|
||||||
|
if getattr(self, "LOG_PATH", None) is None:
|
||||||
|
project_dir = getattr(self, "PROJECT_DIR", default_project_dir())
|
||||||
|
version_check = getattr(self, "VERSION_CHECK", True)
|
||||||
|
object.__setattr__(self, "LOG_PATH", default_log_path(project_dir, version_check))
|
||||||
|
|
||||||
|
# Support console DO NOT TRACK initiative.
|
||||||
|
if os.getenv("DO_NOT_TRACK", "").lower() in ("1", "t", "true", "y", "yes"):
|
||||||
|
object.__setattr__(self, "SEND_ANONYMOUS_USAGE_STATS", False)
|
||||||
|
|
||||||
|
# Check mutual exclusivity once all flags are set.
|
||||||
|
self._assert_mutually_exclusive(
|
||||||
|
params_assigned_from_default, ["WARN_ERROR", "WARN_ERROR_OPTIONS"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Support lower cased access for legacy code.
|
||||||
|
params = set(
|
||||||
|
x for x in dir(self) if not callable(getattr(self, x)) and not x.startswith("__")
|
||||||
|
)
|
||||||
|
for param in params:
|
||||||
|
object.__setattr__(self, param.lower(), getattr(self, param))
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return str(pf(self.__dict__))
|
return str(pf(self.__dict__))
|
||||||
|
|
||||||
|
def _override_if_set(self, lead: str, follow: str, defaulted: Set[str]) -> None:
|
||||||
|
"""If the value of the lead parameter was set explicitly, apply the value to follow, unless follow was also set explicitly."""
|
||||||
|
if lead.lower() not in defaulted and follow.lower() in defaulted:
|
||||||
|
object.__setattr__(self, follow.upper(), getattr(self, lead.upper(), None))
|
||||||
|
|
||||||
|
def _assert_mutually_exclusive(
|
||||||
|
self, params_assigned_from_default: Set[str], group: List[str]
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Ensure no elements from group are simultaneously provided by a user, as inferred from params_assigned_from_default.
|
||||||
|
Raises click.UsageError if any two elements from group are simultaneously provided by a user.
|
||||||
|
"""
|
||||||
|
set_flag = None
|
||||||
|
for flag in group:
|
||||||
|
flag_set_by_user = flag.lower() not in params_assigned_from_default
|
||||||
|
if flag_set_by_user and set_flag:
|
||||||
|
raise DbtUsageException(
|
||||||
|
f"{flag.lower()}: not allowed with argument {set_flag.lower()}"
|
||||||
|
)
|
||||||
|
elif flag_set_by_user:
|
||||||
|
set_flag = flag
|
||||||
|
|
||||||
|
def fire_deprecations(self):
|
||||||
|
"""Fires events for deprecated env_var usage."""
|
||||||
|
[dep_fn() for dep_fn in self.deprecated_env_var_warnings]
|
||||||
|
# It is necessary to remove this attr from the class so it does
|
||||||
|
# not get pickled when written to disk as json.
|
||||||
|
object.__delattr__(self, "deprecated_env_var_warnings")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, command: CliCommand, args_dict: Dict[str, Any]) -> "Flags":
|
||||||
|
command_arg_list = command_params(command, args_dict)
|
||||||
|
ctx = args_to_context(command_arg_list)
|
||||||
|
flags = cls(ctx=ctx)
|
||||||
|
flags.fire_deprecations()
|
||||||
|
return flags
|
||||||
|
|
||||||
|
|
||||||
|
CommandParams = List[str]
|
||||||
|
|
||||||
|
|
||||||
|
def command_params(command: CliCommand, args_dict: Dict[str, Any]) -> CommandParams:
|
||||||
|
"""Given a command and a dict, returns a list of strings representing
|
||||||
|
the CLI params for that command. The order of this list is consistent with
|
||||||
|
which flags are expected at the parent level vs the command level.
|
||||||
|
|
||||||
|
e.g. fn("run", {"defer": True, "print": False}) -> ["--no-print", "run", "--defer"]
|
||||||
|
|
||||||
|
The result of this function can be passed in to the args_to_context function
|
||||||
|
to produce a click context to instantiate Flags with.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd_args = set(command_args(command))
|
||||||
|
prnt_args = set(parent_args())
|
||||||
|
default_args = set([x.lower() for x in FLAGS_DEFAULTS.keys()])
|
||||||
|
|
||||||
|
res = command.to_list()
|
||||||
|
|
||||||
|
for k, v in args_dict.items():
|
||||||
|
k = k.lower()
|
||||||
|
|
||||||
|
# if a "which" value exists in the args dict, it should match the command provided
|
||||||
|
if k == WHICH_KEY:
|
||||||
|
if v != command.value:
|
||||||
|
raise DbtInternalError(
|
||||||
|
f"Command '{command.value}' does not match value of which: '{v}'"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# param was assigned from defaults and should not be included
|
||||||
|
if k not in (cmd_args | prnt_args) - default_args:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# if the param is in parent args, it should come before the arg name
|
||||||
|
# e.g. ["--print", "run"] vs ["run", "--print"]
|
||||||
|
add_fn = res.append
|
||||||
|
if k in prnt_args:
|
||||||
|
|
||||||
|
def add_fn(x):
|
||||||
|
res.insert(0, x)
|
||||||
|
|
||||||
|
spinal_cased = k.replace("_", "-")
|
||||||
|
|
||||||
|
if k == "macro" and command == CliCommand.RUN_OPERATION:
|
||||||
|
add_fn(v)
|
||||||
|
elif v in (None, False):
|
||||||
|
add_fn(f"--no-{spinal_cased}")
|
||||||
|
elif v is True:
|
||||||
|
add_fn(f"--{spinal_cased}")
|
||||||
|
else:
|
||||||
|
add_fn(f"--{spinal_cased}={v}")
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
ArgsList = List[str]
|
||||||
|
|
||||||
|
|
||||||
|
def parent_args() -> ArgsList:
|
||||||
|
"""Return a list representing the params the base click command takes."""
|
||||||
|
from dbt.cli.main import cli
|
||||||
|
|
||||||
|
return format_params(cli.params)
|
||||||
|
|
||||||
|
|
||||||
|
def command_args(command: CliCommand) -> ArgsList:
|
||||||
|
"""Given a command, return a list of strings representing the params
|
||||||
|
that command takes. This function only returns params assigned to a
|
||||||
|
specific command, not those of its parent command.
|
||||||
|
|
||||||
|
e.g. fn("run") -> ["defer", "favor_state", "exclude", ...]
|
||||||
|
"""
|
||||||
|
import dbt.cli.main as cli
|
||||||
|
|
||||||
|
CMD_DICT: Dict[CliCommand, ClickCommand] = {
|
||||||
|
CliCommand.BUILD: cli.build,
|
||||||
|
CliCommand.CLEAN: cli.clean,
|
||||||
|
CliCommand.CLONE: cli.clone,
|
||||||
|
CliCommand.COMPILE: cli.compile,
|
||||||
|
CliCommand.DOCS_GENERATE: cli.docs_generate,
|
||||||
|
CliCommand.DOCS_SERVE: cli.docs_serve,
|
||||||
|
CliCommand.DEBUG: cli.debug,
|
||||||
|
CliCommand.DEPS: cli.deps,
|
||||||
|
CliCommand.INIT: cli.init,
|
||||||
|
CliCommand.LIST: cli.list,
|
||||||
|
CliCommand.PARSE: cli.parse,
|
||||||
|
CliCommand.RUN: cli.run,
|
||||||
|
CliCommand.RUN_OPERATION: cli.run_operation,
|
||||||
|
CliCommand.SEED: cli.seed,
|
||||||
|
CliCommand.SHOW: cli.show,
|
||||||
|
CliCommand.SNAPSHOT: cli.snapshot,
|
||||||
|
CliCommand.SOURCE_FRESHNESS: cli.freshness,
|
||||||
|
CliCommand.TEST: cli.test,
|
||||||
|
CliCommand.RETRY: cli.retry,
|
||||||
|
}
|
||||||
|
click_cmd: Optional[ClickCommand] = CMD_DICT.get(command, None)
|
||||||
|
if click_cmd is None:
|
||||||
|
raise DbtInternalError(f"No command found for name '{command.name}'")
|
||||||
|
return format_params(click_cmd.params)
|
||||||
|
|
||||||
|
|
||||||
|
def format_params(params: List[Parameter]) -> ArgsList:
|
||||||
|
return [str(x.name) for x in params if not str(x.name).lower().startswith("deprecated_")]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user