diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..0dea0d1 --- /dev/null +++ b/404.html @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +404 Page not found | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+
+
+

Page not found :(

+

The page you are looking for doesn't exist or has been moved.

+
+
+
+
+
+
+
+ + + + + + + + + + + + + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..422392b --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +databend-internals.psiace.me \ No newline at end of file diff --git a/_headers b/_headers new file mode 100644 index 0000000..c269214 --- /dev/null +++ b/_headers @@ -0,0 +1,2 @@ +/* + Access-Control-Allow-Origin: * \ No newline at end of file diff --git a/android-chrome-192x192.png b/android-chrome-192x192.png new file mode 100644 index 0000000..03a0e70 Binary files /dev/null and b/android-chrome-192x192.png differ diff --git a/android-chrome-512x512.png b/android-chrome-512x512.png new file mode 100644 index 0000000..74bdfb9 Binary files /dev/null and b/android-chrome-512x512.png differ diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..225acef Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/bootstrap/scss/bootstrap-grid.css b/bootstrap/scss/bootstrap-grid.css new file mode 100644 index 0000000..88b068e --- /dev/null +++ b/bootstrap/scss/bootstrap-grid.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap Grid v5.0.0-beta3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{width:100%;padding-right:var(--bs-gutter-x, .75rem);padding-left:var(--bs-gutter-x, .75rem);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container-sm,.container{max-width:540px}}@media (min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media (min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media (min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}.row{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y)*-1);margin-right:calc(var(--bs-gutter-x)/-2);margin-left:calc(var(--bs-gutter-x)/-2)}.row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x)/2);padding-left:calc(var(--bs-gutter-x)/2);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.3333333333%}.col-2{flex:0 0 auto;width:16.6666666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.3333333333%}.col-5{flex:0 0 auto;width:41.6666666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.3333333333%}.col-8{flex:0 0 auto;width:66.6666666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.3333333333%}.col-11{flex:0 0 auto;width:91.6666666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}.g-0,.gx-0{--bs-gutter-x: 0}.g-0,.gy-0{--bs-gutter-y: 0}.g-1,.gx-1{--bs-gutter-x: .25rem}.g-1,.gy-1{--bs-gutter-y: .25rem}.g-2,.gx-2{--bs-gutter-x: .5rem}.g-2,.gy-2{--bs-gutter-y: .5rem}.g-3,.gx-3{--bs-gutter-x: 1rem}.g-3,.gy-3{--bs-gutter-y: 1rem}.g-4,.gx-4{--bs-gutter-x: 1.5rem}.g-4,.gy-4{--bs-gutter-y: 1.5rem}.g-5,.gx-5{--bs-gutter-x: 3rem}.g-5,.gy-5{--bs-gutter-y: 3rem}@media (min-width: 576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.3333333333%}.col-sm-2{flex:0 0 auto;width:16.6666666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.3333333333%}.col-sm-5{flex:0 0 auto;width:41.6666666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.3333333333%}.col-sm-8{flex:0 0 auto;width:66.6666666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.3333333333%}.col-sm-11{flex:0 0 auto;width:91.6666666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x: 0}.g-sm-0,.gy-sm-0{--bs-gutter-y: 0}.g-sm-1,.gx-sm-1{--bs-gutter-x: .25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y: .25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x: .5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y: .5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x: 1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y: 1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x: 1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y: 1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x: 3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y: 3rem}}@media (min-width: 768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.3333333333%}.col-md-2{flex:0 0 auto;width:16.6666666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.3333333333%}.col-md-5{flex:0 0 auto;width:41.6666666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.3333333333%}.col-md-8{flex:0 0 auto;width:66.6666666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.3333333333%}.col-md-11{flex:0 0 auto;width:91.6666666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}.g-md-0,.gx-md-0{--bs-gutter-x: 0}.g-md-0,.gy-md-0{--bs-gutter-y: 0}.g-md-1,.gx-md-1{--bs-gutter-x: .25rem}.g-md-1,.gy-md-1{--bs-gutter-y: .25rem}.g-md-2,.gx-md-2{--bs-gutter-x: .5rem}.g-md-2,.gy-md-2{--bs-gutter-y: .5rem}.g-md-3,.gx-md-3{--bs-gutter-x: 1rem}.g-md-3,.gy-md-3{--bs-gutter-y: 1rem}.g-md-4,.gx-md-4{--bs-gutter-x: 1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y: 1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x: 3rem}.g-md-5,.gy-md-5{--bs-gutter-y: 3rem}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.3333333333%}.col-lg-2{flex:0 0 auto;width:16.6666666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.3333333333%}.col-lg-5{flex:0 0 auto;width:41.6666666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.3333333333%}.col-lg-8{flex:0 0 auto;width:66.6666666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.3333333333%}.col-lg-11{flex:0 0 auto;width:91.6666666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x: 0}.g-lg-0,.gy-lg-0{--bs-gutter-y: 0}.g-lg-1,.gx-lg-1{--bs-gutter-x: .25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y: .25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x: .5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y: .5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x: 1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y: 1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x: 1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y: 1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x: 3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y: 3rem}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.3333333333%}.col-xl-2{flex:0 0 auto;width:16.6666666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.3333333333%}.col-xl-5{flex:0 0 auto;width:41.6666666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.3333333333%}.col-xl-8{flex:0 0 auto;width:66.6666666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.3333333333%}.col-xl-11{flex:0 0 auto;width:91.6666666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x: 0}.g-xl-0,.gy-xl-0{--bs-gutter-y: 0}.g-xl-1,.gx-xl-1{--bs-gutter-x: .25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y: .25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x: .5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y: .5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x: 1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y: 1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x: 1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y: 1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x: 3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y: 3rem}}@media (min-width: 1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.3333333333%}.col-xxl-2{flex:0 0 auto;width:16.6666666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.3333333333%}.col-xxl-5{flex:0 0 auto;width:41.6666666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.3333333333%}.col-xxl-8{flex:0 0 auto;width:66.6666666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.3333333333%}.col-xxl-11{flex:0 0 auto;width:91.6666666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.3333333333%}.offset-xxl-2{margin-left:16.6666666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.3333333333%}.offset-xxl-5{margin-left:41.6666666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.3333333333%}.offset-xxl-8{margin-left:66.6666666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.3333333333%}.offset-xxl-11{margin-left:91.6666666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x: 0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y: 0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x: .25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y: .25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x: .5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y: .5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x: 1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y: 1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x: 1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y: 1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x: 3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y: 3rem}}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}@media (min-width: 576px){.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}}@media (min-width: 768px){.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}}@media (min-width: 992px){.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}}@media (min-width: 1200px){.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}}@media (min-width: 1400px){.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}} \ No newline at end of file diff --git a/bootstrap/scss/bootstrap-reboot.css b/bootstrap/scss/bootstrap-reboot.css new file mode 100644 index 0000000..106dbab --- /dev/null +++ b/bootstrap/scss/bootstrap-reboot.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap Reboot v5.0.0-beta3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) + */*,*::before,*::after{box-sizing:border-box}@media (prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,h5,h4,h3,h2,h1{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width: 1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width: 1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width: 1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width: 1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important} \ No newline at end of file diff --git a/bootstrap/scss/bootstrap-utilities.css b/bootstrap/scss/bootstrap-utilities.css new file mode 100644 index 0000000..2647b49 --- /dev/null +++ b/bootstrap/scss/bootstrap-utilities.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap Utilities v5.0.0-beta3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:hover,.link-primary:focus{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:hover,.link-warning:focus{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media (min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media (min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media (min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-primary{border-color:#0d6efd !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#198754 !important}.border-info{border-color:#0dcaf0 !important}.border-warning{border-color:#ffc107 !important}.border-danger{border-color:#dc3545 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#212529 !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.375rem + 1.5vw) !important}.fs-2{font-size:calc(1.325rem + .9vw) !important}.fs-3{font-size:calc(1.3rem + .6vw) !important}.fs-4{font-size:calc(1.275rem + .3vw) !important}.fs-5{font-size:1.25rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-primary{color:#0d6efd !important}.text-secondary{color:#6c757d !important}.text-success{color:#198754 !important}.text-info{color:#0dcaf0 !important}.text-warning{color:#ffc107 !important}.text-danger{color:#dc3545 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-white{color:#fff !important}.text-body{color:#212529 !important}.text-muted{color:#6c757d !important}.text-black-50{color:rgba(0,0,0,.5) !important}.text-white-50{color:rgba(255,255,255,.5) !important}.text-reset{color:inherit !important}.bg-primary{background-color:#0d6efd !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#198754 !important}.bg-info{background-color:#0dcaf0 !important}.bg-warning{background-color:#ffc107 !important}.bg-danger{background-color:#dc3545 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-body{background-color:#fff !important}.bg-white{background-color:#fff !important}.bg-transparent{background-color:rgba(0,0,0,0) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2rem !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media (min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media (min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}@media (min-width: 1200px){.fs-1{font-size:2.5rem !important}.fs-2{font-size:2rem !important}.fs-3{font-size:1.75rem !important}.fs-4{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}} \ No newline at end of file diff --git a/bootstrap/scss/bootstrap.css b/bootstrap/scss/bootstrap.css new file mode 100644 index 0000000..824c460 --- /dev/null +++ b/bootstrap/scss/bootstrap.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v5.0.0-beta3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #6f42c1;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffc107;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-primary: #0d6efd;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffc107;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255,255,255,.15), rgba(255,255,255,0))}*,*::before,*::after{box-sizing:border-box}@media (prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width: 1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (min-width: 1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (min-width: 1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (min-width: 1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{width:100%;padding-right:var(--bs-gutter-x, .75rem);padding-left:var(--bs-gutter-x, .75rem);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container-sm,.container{max-width:540px}}@media (min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media (min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1140px}}@media (min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}.row{--bs-gutter-x: 1.5rem;--bs-gutter-y: 0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y)*-1);margin-right:calc(var(--bs-gutter-x)/-2);margin-left:calc(var(--bs-gutter-x)/-2)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x)/2);padding-left:calc(var(--bs-gutter-x)/2);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.3333333333%}.col-2{flex:0 0 auto;width:16.6666666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.3333333333%}.col-5{flex:0 0 auto;width:41.6666666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.3333333333%}.col-8{flex:0 0 auto;width:66.6666666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.3333333333%}.col-11{flex:0 0 auto;width:91.6666666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.3333333333%}.offset-2{margin-left:16.6666666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.3333333333%}.offset-5{margin-left:41.6666666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.3333333333%}.offset-8{margin-left:66.6666666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.3333333333%}.offset-11{margin-left:91.6666666667%}.g-0,.gx-0{--bs-gutter-x: 0}.g-0,.gy-0{--bs-gutter-y: 0}.g-1,.gx-1{--bs-gutter-x: .25rem}.g-1,.gy-1{--bs-gutter-y: .25rem}.g-2,.gx-2{--bs-gutter-x: .5rem}.g-2,.gy-2{--bs-gutter-y: .5rem}.g-3,.gx-3{--bs-gutter-x: 1rem}.g-3,.gy-3{--bs-gutter-y: 1rem}.g-4,.gx-4{--bs-gutter-x: 1.5rem}.g-4,.gy-4{--bs-gutter-y: 1.5rem}.g-5,.gx-5{--bs-gutter-x: 3rem}.g-5,.gy-5{--bs-gutter-y: 3rem}@media (min-width: 576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.3333333333%}.col-sm-2{flex:0 0 auto;width:16.6666666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.3333333333%}.col-sm-5{flex:0 0 auto;width:41.6666666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.3333333333%}.col-sm-8{flex:0 0 auto;width:66.6666666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.3333333333%}.col-sm-11{flex:0 0 auto;width:91.6666666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.3333333333%}.offset-sm-2{margin-left:16.6666666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.3333333333%}.offset-sm-5{margin-left:41.6666666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.3333333333%}.offset-sm-8{margin-left:66.6666666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.3333333333%}.offset-sm-11{margin-left:91.6666666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x: 0}.g-sm-0,.gy-sm-0{--bs-gutter-y: 0}.g-sm-1,.gx-sm-1{--bs-gutter-x: .25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y: .25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x: .5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y: .5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x: 1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y: 1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x: 1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y: 1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x: 3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y: 3rem}}@media (min-width: 768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.3333333333%}.col-md-2{flex:0 0 auto;width:16.6666666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.3333333333%}.col-md-5{flex:0 0 auto;width:41.6666666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.3333333333%}.col-md-8{flex:0 0 auto;width:66.6666666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.3333333333%}.col-md-11{flex:0 0 auto;width:91.6666666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.3333333333%}.offset-md-2{margin-left:16.6666666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.3333333333%}.offset-md-5{margin-left:41.6666666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.3333333333%}.offset-md-8{margin-left:66.6666666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.3333333333%}.offset-md-11{margin-left:91.6666666667%}.g-md-0,.gx-md-0{--bs-gutter-x: 0}.g-md-0,.gy-md-0{--bs-gutter-y: 0}.g-md-1,.gx-md-1{--bs-gutter-x: .25rem}.g-md-1,.gy-md-1{--bs-gutter-y: .25rem}.g-md-2,.gx-md-2{--bs-gutter-x: .5rem}.g-md-2,.gy-md-2{--bs-gutter-y: .5rem}.g-md-3,.gx-md-3{--bs-gutter-x: 1rem}.g-md-3,.gy-md-3{--bs-gutter-y: 1rem}.g-md-4,.gx-md-4{--bs-gutter-x: 1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y: 1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x: 3rem}.g-md-5,.gy-md-5{--bs-gutter-y: 3rem}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.3333333333%}.col-lg-2{flex:0 0 auto;width:16.6666666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.3333333333%}.col-lg-5{flex:0 0 auto;width:41.6666666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.3333333333%}.col-lg-8{flex:0 0 auto;width:66.6666666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.3333333333%}.col-lg-11{flex:0 0 auto;width:91.6666666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.3333333333%}.offset-lg-2{margin-left:16.6666666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.3333333333%}.offset-lg-5{margin-left:41.6666666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.3333333333%}.offset-lg-8{margin-left:66.6666666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.3333333333%}.offset-lg-11{margin-left:91.6666666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x: 0}.g-lg-0,.gy-lg-0{--bs-gutter-y: 0}.g-lg-1,.gx-lg-1{--bs-gutter-x: .25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y: .25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x: .5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y: .5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x: 1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y: 1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x: 1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y: 1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x: 3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y: 3rem}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.3333333333%}.col-xl-2{flex:0 0 auto;width:16.6666666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.3333333333%}.col-xl-5{flex:0 0 auto;width:41.6666666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.3333333333%}.col-xl-8{flex:0 0 auto;width:66.6666666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.3333333333%}.col-xl-11{flex:0 0 auto;width:91.6666666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.3333333333%}.offset-xl-2{margin-left:16.6666666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.3333333333%}.offset-xl-5{margin-left:41.6666666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.3333333333%}.offset-xl-8{margin-left:66.6666666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.3333333333%}.offset-xl-11{margin-left:91.6666666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x: 0}.g-xl-0,.gy-xl-0{--bs-gutter-y: 0}.g-xl-1,.gx-xl-1{--bs-gutter-x: .25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y: .25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x: .5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y: .5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x: 1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y: 1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x: 1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y: 1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x: 3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y: 3rem}}@media (min-width: 1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.3333333333%}.col-xxl-2{flex:0 0 auto;width:16.6666666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.3333333333%}.col-xxl-5{flex:0 0 auto;width:41.6666666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.3333333333%}.col-xxl-8{flex:0 0 auto;width:66.6666666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.3333333333%}.col-xxl-11{flex:0 0 auto;width:91.6666666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.3333333333%}.offset-xxl-2{margin-left:16.6666666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.3333333333%}.offset-xxl-5{margin-left:41.6666666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.3333333333%}.offset-xxl-8{margin-left:66.6666666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.3333333333%}.offset-xxl-11{margin-left:91.6666666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x: 0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y: 0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x: .25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y: .25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x: .5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y: .5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x: 1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y: 1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x: 1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y: 1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x: 3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y: 3rem}}.table{--bs-table-bg: rgba(0,0,0,0);--bs-table-striped-color: #212529;--bs-table-striped-bg: rgba(0,0,0,.05);--bs-table-active-color: #212529;--bs-table-active-bg: rgba(0,0,0,.1);--bs-table-hover-color: #212529;--bs-table-hover-bg: rgba(0,0,0,.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #cfe2ff;--bs-table-striped-bg: #c5d7f2;--bs-table-striped-color: #000;--bs-table-active-bg: #bacbe6;--bs-table-active-color: #000;--bs-table-hover-bg: #bfd1ec;--bs-table-hover-color: #000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d7d8da;--bs-table-striped-color: #000;--bs-table-active-bg: #cbccce;--bs-table-active-color: #000;--bs-table-hover-bg: #d1d2d4;--bs-table-hover-color: #000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg: #d1e7dd;--bs-table-striped-bg: #c7dbd2;--bs-table-striped-color: #000;--bs-table-active-bg: #bcd0c7;--bs-table-active-color: #000;--bs-table-hover-bg: #c1d6cc;--bs-table-hover-color: #000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg: #cff4fc;--bs-table-striped-bg: #c5e8ef;--bs-table-striped-color: #000;--bs-table-active-bg: #badce3;--bs-table-active-color: #000;--bs-table-hover-bg: #bfe2e9;--bs-table-hover-color: #000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg: #fff3cd;--bs-table-striped-bg: #f2e7c3;--bs-table-striped-color: #000;--bs-table-active-bg: #e6dbb9;--bs-table-active-color: #000;--bs-table-hover-bg: #ece1be;--bs-table-hover-color: #000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg: #f8d7da;--bs-table-striped-bg: #eccccf;--bs-table-striped-color: #000;--bs-table-active-bg: #dfc2c4;--bs-table-active-color: #000;--bs-table-hover-bg: #e5c7ca;--bs-table-hover-color: #000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #ecedee;--bs-table-striped-color: #000;--bs-table-active-bg: #dfe0e1;--bs-table-active-color: #000;--bs-table-hover-bg: #e5e6e7;--bs-table-hover-color: #000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg: #212529;--bs-table-striped-bg: #2c3034;--bs-table-striped-color: #fff;--bs-table-active-bg: #373b3e;--bs-table-active-color: #fff;--bs-table-hover-bg: #323539;--bs-table-hover-color: #fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;appearance:none}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input:disabled~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280,0,0,.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.btn-check:disabled+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:rgba(0,0,0,0);appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);padding:1rem .75rem}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.form-control.is-valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:valid:focus,.form-control.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#198754;padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#198754}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#198754}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.input-group .form-control.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.form-control.is-invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:invalid:focus,.form-control.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#dc3545;padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#dc3545}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#dc3545}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.input-group .form-control.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn:disabled,.btn.disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.btn-check:active+.btn-secondary,.btn-secondary:active,.btn-secondary.active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.btn-check:active+.btn-secondary:focus,.btn-secondary:active:focus,.btn-secondary.active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.btn-secondary.disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info:disabled,.btn-info.disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light:disabled,.btn-light.disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#0d6efd;background-color:rgba(0,0,0,0)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#198754;border-color:#198754}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#198754;background-color:rgba(0,0,0,0)}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#0dcaf0;background-color:rgba(0,0,0,0)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ffc107;background-color:rgba(0,0,0,0)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#212529;border-color:#212529}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#212529;background-color:rgba(0,0,0,0)}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion: reduce){.collapsing{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}@media (min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu{top:0;right:auto;left:100%}.dropend .dropdown-menu[data-bs-popper]{margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu{top:0;right:100%;left:auto}.dropstart .dropdown-menu[data-bs-popper]{margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn:hover,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn.active{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.btn-group>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.btn-group-vertical>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion: reduce){.nav-link{transition:none}}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:none;border:1px solid rgba(0,0,0,0);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .nav-link:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0)}.nav-tabs .nav-link.active,.nav-tabs .nav-item.show .nav-link{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill>.nav-link,.nav-fill .nav-item{flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.nav-justified .nav-item{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media (min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .nav-link:focus{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .nav-link.active{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280,0,0,.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .nav-link:focus{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .nav-link.active{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255,255,255,.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width: 576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:last-of-type{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid rgba(0,0,0,0);border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,.15) 25%, rgba(0,0,0,0) 25%, rgba(0,0,0,0) 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, rgba(0,0,0,0) 75%, rgba(0,0,0,0));background-size:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width: 576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:rgba(0,0,0,0) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid #d8d8d8;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:rgba(0,0,0,0);border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1040;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.offcanvas-backdrop::before{position:fixed;top:0;left:0;z-index:1039;width:100vw;height:100vh;content:"";background-color:rgba(0,0,0,.5)}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:hover,.link-primary:focus{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:hover,.link-warning:focus{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media (min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media (min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media (min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #dee2e6 !important}.border-0{border:0 !important}.border-top{border-top:1px solid #dee2e6 !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #dee2e6 !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #dee2e6 !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #dee2e6 !important}.border-start-0{border-left:0 !important}.border-primary{border-color:#0d6efd !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#198754 !important}.border-info{border-color:#0dcaf0 !important}.border-warning{border-color:#ffc107 !important}.border-danger{border-color:#dc3545 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#212529 !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.375rem + 1.5vw) !important}.fs-2{font-size:calc(1.325rem + .9vw) !important}.fs-3{font-size:calc(1.3rem + .6vw) !important}.fs-4{font-size:calc(1.275rem + .3vw) !important}.fs-5{font-size:1.25rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-primary{color:#0d6efd !important}.text-secondary{color:#6c757d !important}.text-success{color:#198754 !important}.text-info{color:#0dcaf0 !important}.text-warning{color:#ffc107 !important}.text-danger{color:#dc3545 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-white{color:#fff !important}.text-body{color:#212529 !important}.text-muted{color:#6c757d !important}.text-black-50{color:rgba(0,0,0,.5) !important}.text-white-50{color:rgba(255,255,255,.5) !important}.text-reset{color:inherit !important}.bg-primary{background-color:#0d6efd !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#198754 !important}.bg-info{background-color:#0dcaf0 !important}.bg-warning{background-color:#ffc107 !important}.bg-danger{background-color:#dc3545 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-body{background-color:#fff !important}.bg-white{background-color:#fff !important}.bg-transparent{background-color:rgba(0,0,0,0) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2rem !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media (min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media (min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}@media (min-width: 1200px){.fs-1{font-size:2.5rem !important}.fs-2{font-size:2rem !important}.fs-3{font-size:1.75rem !important}.fs-4{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}} \ No newline at end of file diff --git a/contribute-to-databend/how-to-benchmark/01-graph.png b/contribute-to-databend/how-to-benchmark/01-graph.png new file mode 100644 index 0000000..e3b43fa Binary files /dev/null and b/contribute-to-databend/how-to-benchmark/01-graph.png differ diff --git a/contribute-to-databend/how-to-benchmark/02-compare.png b/contribute-to-databend/how-to-benchmark/02-compare.png new file mode 100644 index 0000000..d8be5fd Binary files /dev/null and b/contribute-to-databend/how-to-benchmark/02-compare.png differ diff --git a/contribute-to-databend/how-to-benchmark/03-status.png b/contribute-to-databend/how-to-benchmark/03-status.png new file mode 100644 index 0000000..9e08efb Binary files /dev/null and b/contribute-to-databend/how-to-benchmark/03-status.png differ diff --git a/contribute-to-databend/how-to-profile/01-pprof-flamegraph.png b/contribute-to-databend/how-to-profile/01-pprof-flamegraph.png new file mode 100644 index 0000000..2ce8ef2 Binary files /dev/null and b/contribute-to-databend/how-to-profile/01-pprof-flamegraph.png differ diff --git a/contribute-to-databend/how-to-profile/02-jeprof-mem.png b/contribute-to-databend/how-to-profile/02-jeprof-mem.png new file mode 100644 index 0000000..ffab3f9 Binary files /dev/null and b/contribute-to-databend/how-to-profile/02-jeprof-mem.png differ diff --git a/contribute-to-databend/tracing-in-databend/01-tracing.svg b/contribute-to-databend/tracing-in-databend/01-tracing.svg new file mode 100644 index 0000000..4721443 --- /dev/null +++ b/contribute-to-databend/tracing-in-databend/01-tracing.svg @@ -0,0 +1,5603 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/contribute-to-databend/tracing-in-databend/02-jaeger-and-opentelemetry.png b/contribute-to-databend/tracing-in-databend/02-jaeger-and-opentelemetry.png new file mode 100644 index 0000000..d80acd6 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/02-jaeger-and-opentelemetry.png differ diff --git a/contribute-to-databend/tracing-in-databend/03-jaeger-dot-graph.png b/contribute-to-databend/tracing-in-databend/03-jaeger-dot-graph.png new file mode 100644 index 0000000..be0d6c7 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/03-jaeger-dot-graph.png differ diff --git a/contribute-to-databend/tracing-in-databend/04-jaeger-span-tracing.png b/contribute-to-databend/tracing-in-databend/04-jaeger-span-tracing.png new file mode 100644 index 0000000..3600e41 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/04-jaeger-span-tracing.png differ diff --git a/contribute-to-databend/tracing-in-databend/05-jaeger-span-info.png b/contribute-to-databend/tracing-in-databend/05-jaeger-span-info.png new file mode 100644 index 0000000..45f95b6 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/05-jaeger-span-info.png differ diff --git a/contribute-to-databend/tracing-in-databend/06-jaeger-span-graph.png b/contribute-to-databend/tracing-in-databend/06-jaeger-span-graph.png new file mode 100644 index 0000000..04cace6 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/06-jaeger-span-graph.png differ diff --git a/contribute-to-databend/tracing-in-databend/07-jaeger-span-compare.png b/contribute-to-databend/tracing-in-databend/07-jaeger-span-compare.png new file mode 100644 index 0000000..76f8e09 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/07-jaeger-span-compare.png differ diff --git a/contribute-to-databend/tracing-in-databend/08-tokio-console.png b/contribute-to-databend/tracing-in-databend/08-tokio-console.png new file mode 100644 index 0000000..d59710a Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/08-tokio-console.png differ diff --git a/contribute-to-databend/tracing-in-databend/09-tokio-console-basic.png b/contribute-to-databend/tracing-in-databend/09-tokio-console-basic.png new file mode 100644 index 0000000..19cab66 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/09-tokio-console-basic.png differ diff --git a/contribute-to-databend/tracing-in-databend/10-tokio-console-sort.png b/contribute-to-databend/tracing-in-databend/10-tokio-console-sort.png new file mode 100644 index 0000000..53004f5 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/10-tokio-console-sort.png differ diff --git a/contribute-to-databend/tracing-in-databend/11-tokio-console-task.png b/contribute-to-databend/tracing-in-databend/11-tokio-console-task.png new file mode 100644 index 0000000..a47e031 Binary files /dev/null and b/contribute-to-databend/tracing-in-databend/11-tokio-console-task.png differ diff --git a/docs/advanced-workshops/index.html b/docs/advanced-workshops/index.html new file mode 100644 index 0000000..bd8f360 --- /dev/null +++ b/docs/advanced-workshops/index.html @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +专题研讨 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/advanced-workshops/paper-list/index.html b/docs/advanced-workshops/paper-list/index.html new file mode 100644 index 0000000..70984e8 --- /dev/null +++ b/docs/advanced-workshops/paper-list/index.html @@ -0,0 +1,566 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +推荐阅读的论文清单 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

推荐阅读的论文清单

+

这里列出部分 Databend 设计和实现过程中参考和借鉴的论文,供感兴趣的朋友阅读。

+
    +
  • Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age
  • +
  • MonetDB/X100: Hyper-Pipelining Query Execution
  • +
  • The Cascades Framework for Query Optimization
  • +
  • Xor Filters: Faster and Smaller Than Bloom and Cuckoo Filters
  • +
  • SAHA: A String Adaptive Hash Table for Analytical Databases
  • +
  • Numerically Stable, Single-Pass, Parallel Statistics Algorithms
  • +
  • Unnesting Arbitrary Queries
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 常见问题解答 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 大纲速览 → +
+
+
+ + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/build-and-more/index.html b/docs/contribute-to-databend/build-and-more/index.html new file mode 100644 index 0000000..9735aca --- /dev/null +++ b/docs/contribute-to-databend/build-and-more/index.html @@ -0,0 +1,604 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +轻松了解 Databend 构建 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

轻松了解 Databend 构建

+

Databend 除了支持本机构建外,还可以使用 build tool 来进行跨平台构建。

+

如何构建 Databend

+

Make

+

Databend 在 Makefile 中封装了大量常见命令。采用 make 构建只会开启默认特性,并且会一次性构建 databend-metadatabend-query 以及 databend-metactl

+

前文 设置好开发环境后。

+
    +
  • 执行 make build 即可轻松构建 debug 版本。
  • +
  • make build-release 则会构建 release 版本,并会采用 objcopy 减少二进制体积。
  • +
+

Cargo

+

使用 cargo 构建的好处在于可以按需开启特性,并灵活控制要构建的目标二进制文件。

+

常用的命令格式如:

+
RUSTFLAGS="--cfg tokio_unstable" cargo build --bin=databend-query --features=tokio-console
+
+

即可构建启用 tokio-console 支持的 databend-query ,使用 RUSTFLAGS="--cfg tokio_unstable" 是因为 tokiotracing 特性还没有稳定下来。

+

Databend features 速览

+
    +
  • simd = ["common-arrow/simd"]:默认开启的特性,启用 arrow2 的 SIMD 支持(meta & query)。
  • +
  • tokio-console = ["common-tracing/console", "common-base/tracing"]:用于 tokio 监控和调试,(meta & query)。
  • +
  • memory-profiling = ["common-base/memory-profiling", "tempfile"]:用于内存分析,(meta & query)。
  • +
  • storage-hdfs = ["opendal/services-hdfs", "common-io/storage-hdfs"]:用于提供 hdfs 支持,(query)。
  • +
  • hive = ["common-hive-meta-store", "thrift", "storage-hdfs"]:用于提供 hive 支持,(query)。
  • +
+

跨平台构建

+

Databend 提供了 build-tool image,可以简化跨平台构建所需工作。

+

示例选用 x86_64-unknown-linux-musl 目标平台,其他支持平台也类似:

+
IMAGE='datafuselabs/build-tool:x86_64-unknown-linux-musl' RUSTFLAGS='-C link-arg=-Wl,--compress-debug-sections=zlib-gabi' ./scripts/setup/run_build_tool.sh cargo build --target x86_6
+4-unknown-linux-musl
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/build-with-pgo/index.html b/docs/contribute-to-databend/build-with-pgo/index.html new file mode 100644 index 0000000..7561a0c --- /dev/null +++ b/docs/contribute-to-databend/build-with-pgo/index.html @@ -0,0 +1,651 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用 PGO 优化 Databend 二进制构建 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

使用 PGO 优化 Databend 二进制构建

+ +

在最近的一个 Issue 中 (#9387),Databend 社区的朋友希望能够通过 PGO 构建性能优化的二进制。让我们一起来看一下如何使用 Rust 构建 PGO 优化后的 Databend 吧!

+

背景知识

+

Profile-guided optimization 是一种编译器优化技术,它会收集程序运行过程中的典型执行数据(可能执行的分支),然后针对内联、条件分支、机器代码布局、寄存器分配等进行优化。

+

引入这一技术的背景是:编译器中的静态分析技术能够在不执行代码的情况下考虑代码优化,从而提高编译产物的性能;但是这些优化并不一定能够完全有效,在缺乏运行时信息的情况下,编译器无法考虑到程序的实际执行。

+

PGO 可以基于应用程序在生产环境中的场景收集数据,从而允许优化器针对较热的代码路径优化速度并针对较冷的代码路径优化大小,为应用程序生成更快和更小的代码。

+

rustc 支持 PGO ,允许创建内置数据收集的二进制文件,然后在运行过程中收集数据,从而为最终的编译优化做准备。其实现完全依赖 LLVM 。

+

典型过程

+

构建 PGO 优化的二进制文件通常需要进行以下几步工作:

+
    +
  1. 构建内置数据收集的二进制文件
  2. +
  3. 运行并收集数据,数据会以 .proraw 的形式存在
  4. +
  5. .proraw 文件转换为 .prodata 文件
  6. +
  7. 根据 .prodata 文件进行构建优化
  8. +
+

前置准备

+

运行过程中的收集到的数据最终需要使用 llvm-profdata 进行转换,经由 rustup 安装 llvm-tools-preview 组件可以提供 llvm-profdata ,或者也可以考虑使用最近版本的 LLVM 和 Clang 提供的这一程序。

+
rustup component add llvm-tools-preview
+
+

安装之后的 llvm-profdata 可能需要被添加到 PATH ,路径如下:

+
~/.rustup/toolchains/<toolchain>/lib/rustlib/<target-triple>/bin/
+
+

具体步骤

+

这里并没有选用某个具体生产环境的工作负载,而是使用 Databend 的 SQL 逻辑测试作为一个示范。在性能上可能并不具有积极意义,但可以帮助我们了解如何进行这一过程。

+
+

特别提示: 提供给程序的数据样本必须在统计学上代表典型的使用场景; 否则,反馈有可能损害最终构建的整体性能。

+
+
    +
  1. +

    清除旧数据

    +
    rm -rf /tmp/pgo-data
    +
    +
  2. +
  3. +

    编译支持收集 profile 数据的 release ,使用 RUSTFLAGS 可以将 PGO 编译标志应用到所有 crates 的编译中。

    +
    RUSTFLAGS="-Cprofile-generate=/tmp/pgo-data" \    
    +cargo build --release --target=x86_64-unknown-linux-gnu
    +
    +
  4. +
  5. +

    运行编译好的程序,实际情况下推荐使用符合生产环境典型工作负载的数据集和查询。

    +
      +
    • 通过脚本启动 Databend 单机节点,考虑到生产环境更多是以集群模式运行,也可以启动 Databend 集群。
    • +
    • 导入数据集并运行典型的查询工作负载。
    • +
    +

    示例中选择执行 SQL 逻辑测试,仅供参考。

    +
    BUILD_PROFILE=release ./scripts/ci/deploy/databend-query-standalone.sh 
    +ulimit -n 10000;ulimit -s 16384; cargo run -p sqllogictests --release -- --enable_sandbox --parallel 16 --no-fail-fast
    +
    +
  6. +
  7. +

    使用 llvm-profdata 合并 profile 数据

    +
    llvm-profdata merge -o /tmp/pgo-data/merged.profdata /tmp/pgo-data
    +
    +
  8. +
  9. +

    在 profile 数据指导下进行编译,其实可以注意到,两次编译都使用 --release 标志,因为实际运行情况下,我们总是使用 release 构建的二进制。

    +
    RUSTFLAGS="-Cprofile-use=/tmp/pgo-data/merged.profdata -Cllvm-args=-pgo-warn-missing-function" \    
    +cargo build --release --target=x86_64-unknown-linux-gnu
    +
    +
  10. +
  11. +

    再次运行编译好的程序,运行之前的工作负载以检查性能。

    +
    BUILD_PROFILE=release ./scripts/ci/deploy/databend-query-standalone.sh 
    +ulimit -n 10000;ulimit -s 16384; cargo run -p sqllogictests --release -- --enable_sandbox --parallel 16 --no-fail-fast
    +
    +
  12. +
+

参考资料

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/development-environment/index.html b/docs/contribute-to-databend/development-environment/index.html new file mode 100644 index 0000000..60a95b9 --- /dev/null +++ b/docs/contribute-to-databend/development-environment/index.html @@ -0,0 +1,739 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何设置 Databend 开发环境 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

如何设置 Databend 开发环境

+

工欲善其事,必先利其器。在开启 Databend 贡献之旅前,一起来配置适合自己的开发环境吧。

+

快速安装

+

为方便开发者快速建立开发环境,Databend 维护了一个复杂的 shell 脚本,位于 scripts/setup/dev_setup.sh

+

只需执行一条指令即可完成开发环境配置:

+
$ make setup -d
+
+
+

注意:此过程会辅助安装部分 python 环境,可能会对本地原开发环境造成影响,建议预先执行以下命令以创建并启用专属虚拟环境。

+
$ python -m venv .databend
+$ source .databend/bin/activate
+
+
+

如果遇到依赖缺失问题,可以参考「分步安装 - 测试必备」这一部分的内容安装。

+

分步安装

+

这里以 Fedora 36 为例,考虑到不同系统和发行版之间的差异,你可能需要自行安装 gccpythonopenssl

+

安装 Rust toolchain

+

推荐使用 rustup 来管理 Rust toolchain ,参考 https://rustup.rs/ 进行安装。

+

对于 MacOS 和 Linux 用户,执行:

+
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+
+

Databend 通常使用最新发布的 nightly 工具链进行开发,相关信息记录在 rust-toolchain.toml 中。

+

Rustup 会在使用时对工具链进行自动重载,安装时只需默认配置。

+
$ cargo build
+info: syncing channel updates for 'nightly-2022-05-19-x86_64-unknown-linux-gnu'
+info: latest update on 2022-05-19, rust version 1.63.0-nightly (cd282d7f7 2022-05-18)
+
+

安装必备依赖

+

以下列出了一些安装构建和测试必备依赖的关键步骤,说明及报错信息以注释形式呈现。

+

构建必备

+
# common-hive-meta-store 需要,thrift not found
+$ sudo dnf install thrift
+# openssl-sys 需要,Can't locate FindBin.pm, File/Compare.pm in @INC
+$ sudo dnf install perl-FindBin perl-File-Compare
+# prost-build 需要,is `cmake` not installed?
+# The CMAKE_CXX_COMPILER: c++ is not a full path and was not found in the PATH.,安装 clang 时也会安装 gcc-c++ 和 llvm
+$ sudo dnf install cmake clang
+
+

测试必备

+
# 功能测试和后续体验需要
+$ sudo dnf install mysql
+# 包含目前功能测试和 lint 需要的所有 Python 依赖
+$ cd tests
+$ pip install poetry
+$ poetry install
+$ poetry shell
+# sqllogic 测试需要(包含在上面步骤中,按需选用)
+(tests) $ cd logictest
+$ pip install -r requirements.txt
+# fuzz 测试需要
+(tests) $ cd fuzz
+$ pip install -r requirements.txt
+
+

Lint 必备

+
# taplo fmt 需要
+$ cargo install taplo-cli
+
+

编辑器 - Visual Studio Code

+

Visual Studio Code

+ +

插件推荐

+

rust-analyzer

+
    +
  • 作者:The Rust Programming Language
  • +
  • 为 Visual Studio Code 提供 Rust 语言支持。
  • +
+

crates

+
    +
  • 作者:Seray Uzgur
  • +
  • 帮助 Rust 开发者管理 Cargo.toml 中的依赖。仅支持来源为 crates.io 的依赖。
  • +
+

CodeLLDB

+
    +
  • 作者:Vadim Chugunov
  • +
  • 由 LLDB 驱动的原生调试工具。支持调试 C++ 、Rust 和其他编译语言。
  • +
+

Remote - Containers

+
    +
  • 作者:Microsoft
  • +
  • 在 Docker 容器内打开任何文件夹或 Repo ,并利用 Visual Studio Code 的全部功能。
  • +
+

利用 Dev Containers 开发(For Linux)

+

安装「Remote - Containers」插件,打开 Databend 后会看到右下角弹出窗口并提示「Reopen in Container」。

+

安装 Docker

+

根据 Docker Docs - Install 安装并启动对应你发行版的 docker 。

+

Fedora 36 为例,步骤如下:

+
# 移除旧版本 docker
+$ sudo dnf remove docker \
+                  docker-client \
+                  docker-client-latest \
+                  docker-common \
+                  docker-latest \
+                  docker-latest-logrotate \
+                  docker-logrotate \
+                  docker-selinux \
+                  docker-engine-selinux \
+                  docker-engine
+# 设置存储库
+$ sudo dnf -y install dnf-plugins-core
+$ sudo dnf config-manager \
+    --add-repo \
+    https://download.docker.com/linux/fedora/docker-ce.repo
+# 安装 Docker Engine
+$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-compose-plugin
+
+

将当前 User 添加到 'docker' group 中

+

参考 Docker Docs - PostInstallManage Docker as a non-root user 一节配置,可能需要重启。

+

步骤如下:

+
# 添加 docker 用户组
+$ sudo groupadd docker
+# 将用户添加到 docker 这个组中
+$ sudo usermod -aG docker $USER
+# 激活更改
+$ newgrp docker
+# 更改权限以修复 permission denied
+$ sudo chown "$USER":"$USER" /home/"$USER"/.docker -R
+$ sudo chmod g+rwx "$HOME/.docker" -R
+
+

其他步骤

+

启用 Docker :

+
$ sudo systemctl start docker
+
+

点击左下角「打开远程窗口」选中「Reopen in Container」即可体验。

+

其他实用工具推荐

+

这里列出一些可能有助于 Databend 开发的实用工具,根据实际情况按需选用。

+

starship

+

轻量级、反应迅速、可无限定制的高颜值终端!

+ +

starship

+

参考 starship - installation 进行安装。

+
curl -sS https://starship.rs/install.sh | sh
+
+

hyperfine

+

命令行基准测试工具。

+ +

hyperfine

+

参考 hyperfine - installation 进行安装。

+
cargo install hyperfine
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/faq/index.html b/docs/contribute-to-databend/faq/index.html new file mode 100644 index 0000000..a76a32a --- /dev/null +++ b/docs/contribute-to-databend/faq/index.html @@ -0,0 +1,602 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +常见问题解答 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

常见问题解答

+

开发和调试 Databend 时,难免会遇到一些小问题,这里列出一些解决方案供大家参考。

+

OOM(链接时 kill -9)

+

下面是 16g 内存的一台设备编译 Databend 时的 Swap 情况。

+
$> swapon -s
+Filename                                Type            Size            Used            Priority
+/dev/nvme0n1p3                          partition       134217724       32632196        50
+/dev/zram0                              partition       8388604         8388264         100
+
+

通常情况下,OOM 会在链接阶段发生。一些可能有效的解决方案包括:

+
    +
  • 减少并行任务数
  • +
  • 增加内存/Swap
  • +
  • 更换 Linker
  • +
  • 启用新的符号修饰方案
  • +
+

可以参考下面内容:

+ +

protocol_version < metasrv min-compatible

+

用于判断兼容性的代码会检查当前的 tag,可能是 fork 的 tags 落后于 datafuselabs/databend 。

+
$> git fetch git@github.com:datafuselabs/databend.git --tags
+
+

protoc failed: Unknown flag: --experimental_allow_proto3_optional

+

protoc 现在可以随源码一起构建,考虑到发行版中的 protoc 版本不好统一,建议删除并重新构建项目源码。

+

Undefined symbols "_lzma_auto_decoder"

+

提示需要 lzma,安装 xz 或者 lzip 可以解决。

+
Undefined symbols for architecture x86_64:
+    "_lzma_auto_decoder", referenced from:
+           xz2::stream::Stream::new_auto_decoder::hc1bac2a8128d00b2 in databend_query-6ac85c55ade712f3.xz2
+ld: symbol(s) not found for architecture x86_64
+clang: error: linker command failed with exit code 1 (use -v to see invocation)
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/how-to-benchmark/index.html b/docs/contribute-to-databend/how-to-benchmark/index.html new file mode 100644 index 0000000..adac89e --- /dev/null +++ b/docs/contribute-to-databend/how-to-benchmark/index.html @@ -0,0 +1,826 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何对 Databend 进行基准测试 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

如何对 Databend 进行基准测试

+

Databend 的设计目标之一就是保持最佳性能,为了更好观测和评估性能,社区不光提供一套简单的本地基准测试方案,还建立了可视化的持续基准测试。

+

本地基准测试

+

hyperfine 是一种跨平台的命令行基准测试工具,支持预热和参数化基准测试。

+

Databend 建议使用 hyperfine 通过 ClickHouse / MySQL 客户端执行基准测试,本文将使用 MySQL 客户端来介绍它。

+

前期准备

+

进行本地基准测试之前,必须完成以下几项准备工作:

+ +

设计基准测试套件

+

根据你的数据集特征和关键查询设计 SQL 语句,如果需要预先加载数据,请参考 Docs - Load Data

+

为方便示范,这里选用 Continuous Benchmarking - Vectorized Execution Benchmarking 列出的 10 条语句,保存到 bench.sql 中。

+
SELECT avg(number) FROM numbers_mt(100000000000)
+SELECT sum(number) FROM numbers_mt(100000000000)
+SELECT min(number) FROM numbers_mt(100000000000)
+SELECT max(number) FROM numbers_mt(100000000000)
+SELECT count(number) FROM numbers_mt(100000000000)
+SELECT sum(number+number+number) FROM numbers_mt(100000000000)
+SELECT sum(number) / count(number) FROM numbers_mt(100000000000)
+SELECT sum(number) / count(number), max(number), min(number) FROM numbers_mt(100000000000)
+SELECT number FROM numbers_mt(10000000000) ORDER BY number DESC LIMIT 10
+SELECT max(number), sum(number) FROM numbers_mt(1000000000) GROUP BY number % 3, number % 4, number % 5 LIMIT 10
+
+

使用 bash 脚本简化流程

+

下面给出一个 benchmark.sh 范本,可以简化整个基准测试流程:

+
#!/bin/bash
+
+WARMUP=3
+RUN=10
+
+export script="hyperfine -w $WARMUP -r $RUN"
+
+script=""
+function run() {
+        port=$1
+        sql=$2
+        result=$3
+        script="hyperfine -w $WARMUP -r $RUN"
+        while read SQL; do
+                n="-n \"$SQL\" "
+                s="echo \"$SQL\" | mysql -h127.0.0.1 -P$port -uroot -s"
+                script="$script '$n' '$s'"
+        done <<< $(cat $sql)
+
+        script="$script  --export-markdown $result"
+        echo $script | bash -x
+}
+
+
+run "$1" "$2" "$3"
+
+

在这个脚本中:

+
    +
  • 使用 -w/--warmup & WARMUP 在实际基准测试之前运行 3 次程序执行来预热。
  • +
  • 使用 -r/--runs & RUN 要求执行 10 次基准测试。
  • +
  • 允许指定 Databend MySQL 兼容服务的端口。
  • +
  • 允许指定输入的 SQL 文件,以及输出时的 Markdown 文件。
  • +
+

在使用前需要先运行 chmod a+x ./benchmark.sh 赋予其可执行权限。

+

用法如下所示:

+
./benchmark.sh <port> <sql> <result>
+
+

执行基准测试并获取结果

+

在这个例子中,MySQL 兼容服务的端口是 3307 ,基准测试用到的 SQL 文件为 bench.sql , 预期的输出在 databend-hyperfine.md

+
./benchmark.sh 3307 bench.sql databend-hyperfine.md
+
+

当然,你可以根据自己的配置和需要进行调整。

+
+

注意:下面的示例是在 AMD Ryzen 9 5900HS & 16GB RAM 配置下运行产生,仅供参考。

+
+

终端中的输出如下所示:

+
Benchmark 1:  "SELECT avg(number) FROM numbers_mt(100000000000)"
+  Time (mean ± σ):      3.486 s ±  0.016 s    [User: 0.003 s, System: 0.002 s]
+  Range (min … max):    3.459 s …  3.506 s    10 runs
+
+

最终的结果会保存在 databend-hyperfine.md 中,如下所示。

+ + + + + + + + + + + +
CommandMean [s]Min [s]Max [s]Relative
"SELECT avg(number) FROM numbers_mt(100000000000)"3.690 ± 0.1933.4254.0862.58 ± 0.16
"SELECT sum(number) FROM numbers_mt(100000000000)"3.660 ± 0.1563.3863.9112.56 ± 0.13
"SELECT min(number) FROM numbers_mt(100000000000)"9.581 ± 0.1589.2469.8846.69 ± 0.23
"SELECT max(number) FROM numbers_mt(100000000000)"6.388 ± 0.1426.2036.6244.46 ± 0.17
"SELECT count(number) FROM numbers_mt(100000000000)"2.647 ± 0.1082.4242.7571.85 ± 0.09
"SELECT sum(number+number+number) FROM numbers_mt(100000000000)"19.408 ± 1.12517.85721.61613.55 ± 0.89
"SELECT sum(number) / count(number) FROM numbers_mt(100000000000)"3.869 ± 0.1333.6004.0732.70 ± 0.12
"SELECT sum(number) / count(number), max(number), min(number) FROM numbers_mt(100000000000)"15.488 ± 0.26315.13316.06410.81 ± 0.38
"SELECT number FROM numbers_mt(10000000000) ORDER BY number DESC LIMIT 10"2.971 ± 0.0852.8713.1862.07 ± 0.09
"SELECT max(number), sum(number) FROM numbers_mt(1000000000) GROUP BY number % 3, number % 4, number % 5 LIMIT 10"1.432 ± 0.0441.3991.5451.00
+

持续基准测试

+

Databend 的持续基准测试由 GitHub Action + Vercel + DatabendCloud 强力驱动,在 datafuselabs/databend-perf 这个 repo 中开源了源代码和 Workflow 。

+

基本介绍

+

项目布局

+
.
+├── .github/workflows    # 持续集成工作流
+├── benchmarks           # YAML 格式的 SQL Query 测试套件
+├── collector            # 分类存放性能数据
+├── front                # 可视化前端
+├── reload               # YAML 格式的 Data Load 测试套件
+└── script               # 数据预处理脚本
+
+

Workflow

+

持续基准测试工作流定时计划执行,Perf Workflow 会在每天 00:25 UTC(北京时间 08:25)执行,Reload Workflow 会在每周五 08:25 UTC(北京时间 16:25)执行。

+
    +
  1. 通过 GitHub API 获取当前日期和最新版本的 TAG 。
  2. +
  3. 利用 perf-tool 和 DatabendCloud 进行交互,运行测试。
  4. +
  5. 持久化性能数据到 databend-perf 这一 repo 中 。
  6. +
  7. 执行脚本处理数据,使之生成前端需要的格式。
  8. +
  9. 构建前端,完成可视化。
  10. +
+

测试套件

+

databend-perf 中的测试套件分为 Query Benchmark 和 Load Benchmark 两类,前者放在 benchmarks 目录下,后者放在 reload 目录下。

+

测试用 YAML 格式定义:

+
metadata:
+  table: numbers
+
+statements:
+  - name: Q1
+    query: "SELECT avg(number) FROM numbers_mt(10000000000);"
+
+

metadata 中的 table 字段是必须的,且分配给每类 benchmark 的值都唯一。statements 则只需要指定 namequery

+

向量化执行基准测试

+

定义在 benchmarks/numbers.yaml ,一组数值计算 SQL,利用 Databend 的 numbers 表函数提供百亿级别的数据量。

+

完整语句也可以在 Continuous Benchmarking - Vectorized Execution Benchmarking 查看。

+

Ontime 常见分析场景基准测试

+

定义在 benchmarks/ontime.yaml ,一组常见的空中交通分析 SQL ,基于美国交通部公开的 OnTime 数据集,共计 202,687,654 条记录。

+

当前此基准测试不包含 JOIN 语句,Q5、Q6、Q7 均采用优化后的形式。

+

完整语句也可以在 Continuous Benchmarking - Ontime Benchmarking 查看。

+

Ontime 数据集载入基准测试

+

定义在 reload/ontime.yaml ,同样基于美国交通部公开的 OnTime 数据集,通过 s3 进行 COPY INTO 。

+

关键语句:

+
COPY INTO ontime FROM 's3://<bucket>/m_ontime/' 
+credentials=(aws_key_id='AWS_KEY_ID' aws_secret_key='AWS_SECRET_KEY') 
+pattern ='.*[.]csv' file_format=(type='CSV' field_delimiter='\t' record_delimiter='\n' skip_header=1);
+
+

上面 SQL 语句中的 m_ontime/ 目录即为数据集:由原来 60.8 GB 数据全部合并后,再拆分成 100 个大小相近的文件。

+

数据处理

+

基准测试得到的数据是 Json 格式的,会分类存放到 collector 这个目录下。

+

metadata 部分是包含表、版本、机器规格的信息;schema 部分则是对每条语句执行情况的统计,包括中位数、平均数等。

+

示例:

+
{
+    "metadata":{
+        "table":"numbers",
+        "tag":"v0.7.92-nightly",
+        "size":"Large"
+    },
+    "schema":[
+        {
+            "name":"Q1",
+            "sql":"SELECT avg(number) FROM numbers_mt(10000000000);",
+            "min":0.305,
+            "max":0.388,
+            "median":0.354,
+            "std_dev":0.02701407040784487,
+            "read_row":10000000000,
+            "read_byte":80000000000,
+            "time":[
+                0.315,
+                0.326,
+                ...
+            ],
+            "error":[
+
+            ],
+            "mean":0.34774024905853534
+        },
+        ...
+    ]
+}   
+
+

经由 stript/transform.go 处理,为每个查询的对应图表聚合数据,主要强调最大、最小、均值、中位数四个指标。

+

示例:

+
{
+    "title":"Q1",
+    "sql":"SELECT avg(number) FROM numbers_mt(10000000000);",
+    "lines":[
+        {
+            "name":"min",
+            "data":[
+                3.084,
+                3.097,
+                3.043,
+                ...
+            ],
+        ...
+        }
+    ],
+    "version":[
+        "v0.7.0-nightly",
+        "v0.7.1-nightly",
+        "v0.7.2-nightly",
+        ...
+    ],
+    "legend":[
+        "min",
+        ...
+    ], 
+    "xAxis":[
+        "2022-03-28",
+        "2022-03-29",
+        "2022-03-30",
+        ...
+    ],
+}
+
+

可视化

+

目前可视化方案采用 React + Echarts 实现,每个图表都对应上面处理得到的一个 Json 文件。在添加新的基准测试后,无需修改前端即可展现新的图表。

+

Graphs

+

graphs

+
    +
  • 以折线图的形式展示性能变化,并支持通过拖动图表下方的选择器调整展示的时间区间。
  • +
  • 横轴为日期,纵轴为执行用时,鼠标悬浮到上方即可查看当次执行的信息。
  • +
+

Compare

+

compare

+
    +
  • 支持任选两天对比执行用时的变化,以百分比形式展示。
  • +
+

Status

+

status

+
    +
  • 关注当前最新性能测试结果中各指标的情况,以柱型图展示。
  • +
  • 横轴为不同类型,纵轴为执行用时。
  • +
+

后续优化

+

目前 https://perf.databend.rs 为 Databend 提供了基本的持续性能监控方案,但仍然需要关注以下几个方向的内容:

+
    +
  • 选取更有代表性的指标 执行次数较少(只有 10 次),可供选择的指标可能不够具有代表性。例如:将次数提高到 100 次以获取 P90 来替代当前使用的中位数可能是比较合适的。
  • +
  • 增加性能测试场景的覆盖 后续可以继续新增对其他数据集和场景的性能测试,比如 ssb 、hits 。
  • +
  • 丰富性能监控的方向 监控 IO 和网络性能表现,对部分重点查询提供额外的性能评估,比如解析 Json 的性能表现。
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/how-to-contribute/index.html b/docs/contribute-to-databend/how-to-contribute/index.html new file mode 100644 index 0000000..2ce267c --- /dev/null +++ b/docs/contribute-to-databend/how-to-contribute/index.html @@ -0,0 +1,748 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何参与 Databend 开源协作 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

如何参与 Databend 开源协作

+

Databend 是一个开源的云数仓项目,这意味着你可以轻松参与“设计->研发->使用->反馈”的整个链路。这篇文章总结了参与 Databend 开源协作时需要注意的一些事项,以使贡献流程更加清晰和透明。

+

在这篇文章中,主要从“沟通”和“实施”两个方面介绍 Databend 的开源协作。

+

沟通

+

沟通是参与开源的重要环节,正是得益于开源世界中沟通的公开与透明,才能迸发出如此生机与活力。

+

参与 Databend 开源协作的主要沟通方式有 Issues 、RFCs 以及 Channels 三种。

+

Issues

+

Issues 通常用于 Bug 反馈和新特性请求,Databend 使用 GitHub Issues 来跟踪和管理这些反馈。

+

https://github.com/datafuselabs/databend/issues/new/choose

+

Bug 反馈

+

Databend 提供了一个基本的 Bug 反馈模板以确保沟通的顺畅进行。

+

在进行 Bug 反馈之前,请检索是否存在已知的解决方案,并确定你正在运行的版本,最好包含 commit id

+

当然,提供清晰的问题描述和可复现步骤也是非常重要的环节。

+

好例子:https://github.com/datafuselabs/databend/issues/6564

+
+

Databend 的迭代速度非常快,每天都会发布新的 nightly 供用户尝鲜,建议尝试新版本以确定能否复现。

+
+

新特性请求

+

对于新特性请求,请尽可能提供详细的描述或是预期的行为,如果有可以参考的文档就更好了。

+

好例子:https://github.com/datafuselabs/databend/issues/5979

+

RFCs

+

对于小的功能点,打开 Issues 进行沟通就足够了。而大的功能、设计上的变动或者是需要充分讨论和同步的想法,请以 RFC 的形式提交。

+

在设计和沟通的早期阶段,推荐使用 Discusssions 进行讨论。

+

一旦确认实施和落地,提交 RFC 文档并建立用于跟踪的 Issues 则是更为合适的做法。

+

好例子:https://github.com/datafuselabs/databend/discussions/5438

+

Channels

+

Channels 通常用于一般的交流和讨论,Databend 团队鼓励使用 GitHub Discusssions 进行一般问题的咨询和讨论,形成一个“知识库”,方便检索和参与。

+

当然,Databend 也提供 Slack 频道和微信群用于日常交流与讨论。

+

Slack:https://link.databend.rs/join-slack

+

实施

+

进入到实施环节,你将亲自动手改进 Databend 的代码或者文档,并踏上成为 Databend 维护者的道路,接下来让我们一起看一下有哪些环节需要重视。

+

编码

+

前置环境

+

在「Databend 贡献之路 | 如何设置 Databend 开发环境」一文中,已经详细介绍过如何配置 Databend 开发环境。

+

需要注意的是,考虑到不同系统和发行版之间的差异,你可能需要自行安装 gccpythonopenssl 等相关基础程序。

+

代码文档

+

任何公共字段、函数和方法都应该用 Rustdoc 进行文档化。

+

必要的时候,请使用 Diagon 或其他 ASCII 图像生成器生成示意图以对设计进行充分描述。

+

下面给出一个简单的例子:

+
/// Represents (x, y) of a 2-dimensional grid
+///
+/// A line is defined by 2 instances.
+/// A plane is defined by 3 instances.
+#[repr(C)]
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+

文档

+

前置环境

+

本机上需要安装有用于 node 环境管理的 nvm ,以及用于 node 依赖管理的 yarn

+

文档的开发环节需要进入 website 目录后根据 README.md 中的描述进行配置。

+

通常情况下,包含以下步骤:

+
    +
  • 使用指定的 node 版本:nvm use
  • +
  • 安装依赖:yarn install
  • +
  • 本地预览:yarn start
  • +
+

重要提示

+

文档应当正确放置在 docs 目录下,请本地预览确认无误后再进行提交。

+

最终文档会托管到 https://databend.rs

+

准备

+

License 检查

+

如果引入了新文件,建议执行 License 以确认是否添加了合适的许可信息。对于非代码文件,可能需要编辑 .licenserc.yaml 以跳过检查。

+
make check-license
+
+

代码风格

+

请运行下述命令以完成代码风格的统一:

+
make lint
+
+

依赖审计

+

如果你引入了一些新的依赖项,可以使用:

+
cargo udeps --workspace
+cargo audit
+
+

测试

+

在「Databend 贡献之路 | 如何为 Databend 添加新的测试」中,已经对测试做了详细的描述。

+

通常情况下,使用 make test 一次性执行 单元测试功能测试 就可以。

+

但是,也建议执行 集群 相关的测试,以确保分布式执行不会出现差错。

+

拉请求(Pull Request)

+

一般流程

+
    +
  • 分叉 Databend 的 repo 并从 main 创建分支。
  • +
  • 如果不存在跟踪的问题,请打开一个对应的问题并提供上下文
  • +
  • 提交一份「草稿请求(Draft Pull Request)」,以标记你正在进行相关工作。
  • +
  • 如果涉及编写代码,请添加对应的单元测试。
  • +
  • 进行测试,验证并确保测试套件通过。
  • +
  • 确保代码能够通过风格审计,make lint
  • +
  • 将状态更改为「准备检查(Ready for review)」。
  • +
  • 当心 @mergify 的回复,它会提供一些指导。
  • +
+

PR 标题填写

+

PR 标题需要符合 <类型>(<范围>): <描述> 的约束。

+
fix(query): fix group by string bug
+^--^  ^------------^
+|     |
+|     +-> Summary in present tense.
+|
++-------> Type: feat, fix, refactor, ci, build, docs, website, chore
+
+

PR 模板填写

+

Databend 提供了一个基本的 PR 模板,请不要修改模板上下文,并填充对本次 PR 的总结信息,包括是否修复/修复了哪个已知的 Issue 。

+
I hereby agree to the terms of the CLA available at: https://databend.rs/dev/policies/cla/
+
+## Summary
+
+Summary about this PR
+
+Fixes #issue
+
+

好例子:https://github.com/datafuselabs/databend/pull/6665

+

持续集成

+

持续集成相关的文件位于 .github 目录中的 actionsworkflows 目录下。

+

文档

+

文档相关的持续集成会通过 Vercel 进行,需要关注 Status ,并点击 Visit Preview 查看渲染情况。

+

检查

+

包括 License 检查、代码风格检查、依赖关系审计等内容。

+
    +
  • Dev Linux / check (pull_request)
  • +
+

构建

+

主要是测试跨平台构建,主要是针对 x86_64 和 aarch64 架构,对 Linux 的 GNU 和 MUSL 支持处于第一优先级别。MacOS 虽然标记为 optional ,但是需要尽量保证。

+
    +
  • Dev MacOS / build_x86_64_macos(optional)
  • +
  • Dev MacOS / build_aarch64_macos(optional)
  • +
  • Dev Linux / build_x86_64_gnu (pull_request)
  • +
  • Dev Linux / build_aarch64_gnu (pull_request)
  • +
  • Dev Linux / build_x86_64_musl (pull_request)
  • +
  • Dev Linux / build_aarch64_musl (pull_request)
  • +
  • Dev Linux / build_hive (pull_request)
  • +
+

测试

+

主要是执行各种测试确保代码和功能都符合要求,包括单元测试、功能测试、分布式测试、模糊测试等:

+
    +
  • Dev Linux / test_unit (pull_request)
  • +
  • Dev Linux / test_metactl (pull_request)
  • +
  • Dev Linux / test_compat (pull_request)
  • +
  • Dev Linux / test_meta_cluster (pull_request)
  • +
  • Dev Linux / test_stateless_standalone_linux (pull_request)
  • +
  • Dev Linux / test_stateless_cluster_linux (pull_request)
  • +
  • Dev Linux / test_sqllogic_standalone_linux (pull_request)
  • +
  • Dev Linux / test_stateful_standalone_linux (pull_request)
  • +
  • Dev Linux / test_fuzz_standalone_linux (pull_request)
  • +
  • Dev Linux / test_stateful_hive_standalone (pull_request)
  • +
  • Dev MacOS / test_stateless_cluster_macos(optional)
  • +
+

合并

+

有两位或两位以上维护者投下赞同票,并满足下述条件,Mergify 将会帮助我们完成代码合并工作:

+
    +
  • 所有测试都通过
  • +
  • 所有审核意见都已经解决
  • +
  • 没有代码冲突
  • +
+

在合并之后,你的 git name 将收集在 Databend 的 system.contributors 表中,在新版本 release 之后,执行 SELECT * FROM system.contributors 即可查看。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/how-to-profile/index.html b/docs/contribute-to-databend/how-to-profile/index.html new file mode 100644 index 0000000..f122382 --- /dev/null +++ b/docs/contribute-to-databend/how-to-profile/index.html @@ -0,0 +1,703 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 性能剖析方法与工具 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 性能剖析方法与工具

+

Databend 整合了一些性能剖析工具,可以方便进行深入分析。本文将会介绍如何进行 CPU / Memory Profiling 。

+

CPU Profiling

+

CPU 分析,按照一定的频率采集所监听的应用程序 CPU(含寄存器)的使用情况,可确定应用程序在主动消耗 CPU 周期时花费时间的位置。

+

pprof 是 Google 开源的代码性能分析工具,可以直接生成代码分析报告,不仅支持通过命令式交互查看,也便于可视化展示。Databend 使用 pprof-rs 完成对 pprof 工具的支持。

+

必备工作

+
    +
  • CPU 分析属于 Databend 的内置能力,部署一个 Databend 实例即可开始使用。
  • +
+

命令行交互

+
go tool pprof http://localhost:<your-databend-port>/debug/pprof/profile?seconds=<your-profile-second>
+
+

若 http 端口为 8080 ,采样时间为 20 秒,结果示例如下:

+
$ go tool pprof http://localhost:8080/debug/pprof/profile?seconds=20
+Fetching profile over HTTP from http://localhost:8080/debug/pprof/profile?seconds=20
+Saved profile in ~/pprof/pprof.samples.cpu.001.pb.gz
+Type: cpu
+Time: Jul 15, 2022 at 9:45am (CST)
+Duration: 20s, Total samples = 141.41ms ( 0.71%)
+Entering interactive mode (type "help" for commands, "o" for options)
+(pprof) top
+Showing nodes accounting for 141.41ms, 100% of 141.41ms total
+Showing top 10 nodes out of 218
+      flat  flat%   sum%        cum   cum%
+  141.41ms   100%   100%   141.41ms   100%  backtrace::backtrace::libunwind::trace
+         0     0%   100%    10.10ms  7.14%  <&mut regex_syntax::utf8::Utf8Sequences as core::iter::traits::iterator::Iterator>::next
+         0     0%   100%    10.10ms  7.14%  <<std::thread::Builder>::spawn_unchecked_<sled::threadpool::queue::spawn_to<sled::pagecache::iterator::scan_segment_headers_and_tail::{closure#0}::{closure#0}, core::option::Option<(u64, sled::pagecache::logger::SegmentHeader)>>::{closure#0}::{closure#0}, ()>::{closure#1} as core::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
+         0     0%   100%    10.10ms  7.14%  <<std::thread::Builder>::spawn_unchecked_<sled::threadpool::queue::spawn_to<sled::pagecache::iterator::scan_segment_headers_and_tail::{closure#0}::{closure#0}, core::option::Option<(u64, sled::pagecache::logger::SegmentHeader)>>::{closure#0}::{closure#1}, ()>::{closure#1} as core::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
+         0     0%   100%    10.10ms  7.14%  <<std::thread::Builder>::spawn_unchecked_<sled::threadpool::queue::spawn_to<sled::pagecache::iterator::scan_segment_headers_and_tail::{closure#0}::{closure#0}, core::option::Option<(u64, sled::pagecache::logger::SegmentHeader)>>::{closure#0}::{closure#2}, ()>::{closure#1} as core::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
+         0     0%   100%    10.10ms  7.14%  <<std::thread::Builder>::spawn_unchecked_<sled::threadpool::queue::spawn_to<sled::pagecache::iterator::scan_segment_headers_and_tail::{closure#0}::{closure#0}, core::option::Option<(u64, sled::pagecache::logger::SegmentHeader)>>::{closure#0}::{closure#3}, ()>::{closure#1} as core::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
+         0     0%   100%    10.10ms  7.14%  <[&str]>::iter
+         0     0%   100%    10.10ms  7.14%  <[(char, &[char])]>::binary_search_by::<<[(char, &[char])]>::binary_search_by_key<char, regex_syntax::unicode::simple_fold::imp::{closure#0}>::{closure#0}>
+         0     0%   100%    10.10ms  7.14%  <[(char, &[char])]>::binary_search_by_key::<char, regex_syntax::unicode::simple_fold::imp::{closure#0}>
+         0     0%   100%    10.10ms  7.14%  <[(char, &[char])]>::binary_search_by_key::<char, regex_syntax::unicode::simple_fold::imp::{closure#0}>::{closure#0}
+
+

可视化

+

执行下述命令可以进行可视化:

+
go tool pprof -http=0.0.0.0:<your-profile-port> <your profile data>
+
+

例如,执行下述语句可以在 8088 端口开启 WEB UI 。

+
go tool pprof -http=0.0.0.0:8088 ~/pprof/pprof.samples.cpu.001.pb.gz 
+
+

访问 http://0.0.0.0:8088/ui/flamegraph 即可得到火焰图。

+

pprof flamegraph

+

注意事项

+

Databend 暂时不支持在 musl 平台上运行 pprof 。

+

Memory Profiling

+

内存分析,在应用程序进行堆分配时记录堆栈追踪,用于监视当前和历史内存使用情况,以及检查内存泄漏。

+

通过与 jemalloc 的集成,Databend 得以整合多种内存分析能力。这里使用 jeprof 进行内存分析。

+

必备工作

+
    +
  • +

    安装 Jemalloc,并启用其剖析能力 ./configure --enable-prof

    +
  • +
  • +

    在构建二进制文件时启用 memory-profiling 特性:cargo build --features memory-profiling

    +
  • +
  • +

    在创建 Databend 实例时,设置环境变量 MALLOC_CONF=prof:true 以启用内存分析。示例:

    +
    MALLOC_CONF=prof:true ./target/debug/databend-query
    +
    +
  • +
+

堆快照转储

+
jeprof <your-profile-target> http://localhost:<your-databend-port>/debug/mem
+
+

下面的例子选用 debug 模式下编译的 databend-query 作为 target,端口为 8080,结果如下所示:

+
$ jeprof ./target/debug/databend-query http://localhost:8080/debug/mem
+Using local file ./target/debug/databend-query.
+Gathering CPU profile from http://localhost:8080/debug/mem/pprof/profile?seconds=30 for 30 seconds to
+  ~/jeprof/databend-query.1658367127.localhost
+Be patient...
+Wrote profile to ~/jeprof/databend-query.1658367127.localhost
+Welcome to jeprof!  For help, type 'help'.
+(jeprof) top
+Total: 11.1 MB
+     6.0  54.6%  54.6%      6.0  54.6% ::alloc_zeroed
+     5.0  45.4% 100.0%      5.0  45.4% ::alloc
+     0.0   0.0% 100.0%      0.5   4.5% ::add_node::{closure#0}
+     0.0   0.0% 100.0%      5.0  45.4% ::alloc_impl
+     0.0   0.0% 100.0%      5.0  45.4% ::allocate
+     0.0   0.0% 100.0%      4.5  40.8% ::allocate_in
+     0.0   0.0% 100.0%      0.5   4.5% ::apply_batch_inner
+     0.0   0.0% 100.0%     11.1 100.0% ::block_on::
+     0.0   0.0% 100.0%     11.1 100.0% ::block_on::::{closure#0}
+     0.0   0.0% 100.0%      0.5   4.5% ::clone
+(jeprof) 
+
+

生成内存分配调用图

+

常见的用例之一是查找内存泄漏,通过比较间隔前后的内存画像即可完成这一工作。

+

在下面的命令行中,以 10s 为间隔,获取前后两个时间节点的内存画像。

+
curl 'http://localhost:<your-databend-port>/debug/mem/pprof/profile?seconds=0' > a.prof
+sleep 10
+curl 'http://localhost:<your-databend-port>/debug/mem/pprof/profile?seconds=0' > b.prof
+
+

接着,可以利用这两份内存画像来生成 pdf 格式的内存分配调用图。

+
jeprof \
+    --show_bytes \
+    --nodecount=1024 \
+    --nodefraction=0.001 \
+    --edgefraction=0.001 \
+    --maxdegree=64 \
+    --pdf \
+    <your-profile-target> \
+    --base=a.prof \
+    b.prof \
+    > mem.pdf
+
+

同样选用 debug 模式下编译的 databend-query 作为 target,端口为 8080,结果如图所示:

+

jeprof call graph

+

注意事项

+

目前无法在 Mac 上进行内存分析,不管是 x86_64 还是 aarch64 平台。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/how-to-write-functions/index.html b/docs/contribute-to-databend/how-to-write-functions/index.html new file mode 100644 index 0000000..922f695 --- /dev/null +++ b/docs/contribute-to-databend/how-to-write-functions/index.html @@ -0,0 +1,682 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何为 Databend 添加新的函数 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

如何为 Databend 添加新的函数

+ +

迁移函数到全新表达式框架

+

如果你对自定义类型系统或者数据库项目的研发感兴趣,可以看看 Databend 是如何做的。

+

现在 Databend 正在尝试将一些旧的函数迁移到全新表达式框架中,你愿意来试试看吗?

+ +

背景

+

近期,Databend 围绕全新表达式框架的设计与实现开展了许多工作,将会带来一些有意思的特性。

+
    +
  • 类型检查
  • +
  • 类型安全的向下转型
  • +
  • 使用 Enum 分发列
  • +
  • 泛型
  • +
+

如何迁移

+

旧的函数位于 query/functions/src/scalars ,它们需要被迁移到 query/functions-v2/src/scalars/

+

通常情况下,旧函数实现中的核心逻辑是可以复用的,只需要进行少量重写使其符合新的实现方案。

+

类似地,旧的测试位于 query/functions/tests/it/scalars/ ,也应该迁移到 query/functions-v2/tests/it/scalars/

+

新测试将会使用 goldenfile 进行编写,所以可以轻松生成测试用例而无需大量繁重的手写工作。

+

示例

+

LENGTH 将会按字节数返回字符串的长度。

+

仅仅使用 6 行,就可以在 query/functions-v2/src/scalars/strings.rs 中实现 LENGTH 函数。

+
registry.register_1_arg::<StringType, NumberType<u64>, _, _>(
+    "length",
+    FunctionProperty::default(),
+    |_| None,
+    |val| val.len() as u64,
+);
+
+

由于 OCTET_LENGTHLENGTH 的同义函数,只需为其添加一个函数别名即可,仅用一行。

+
registry.register_aliases("length", &["octet_length"]);
+
+

接下来,需要写一些测试,来确保函数实现的正确性。编辑 query/functions-v2/tests/it/scalars/string.rs

+
fn test_octet_length(file: &mut impl Write) {
+    run_ast(file, "octet_length('latin')", &[]);
+    run_ast(file, "octet_length(NULL)", &[]);
+    run_ast(file, "length(a)", &[(
+        "a",
+        DataType::String,
+        build_string_column(&["latin", "кириллица", "кириллица and latin"]),
+    )]);
+}
+
+

将其注册到 test_string 函数中:

+
#[test]
+fn test_string() {
+    let mut mint = Mint::new("tests/it/scalars/testdata");
+    let file = &mut mint.new_goldenfile("string.txt").unwrap();
+
+    ...
+    test_octet_length(file);
+    ...
+}
+
+

通过命令行,可以直接生成完整的测试用例,并附加到对应的 goldenflie 中:

+
REGENERATE_GOLDENFILES=1 cargo test -p query-functions-v2 --test it
+
+

请使用 git diff 检查一下生成的测试是否符合预期,如果一切顺利,LENGTH 函数的迁移工作就完成了。

+

函数进阶使用

+
    +
  • 注册方法解析:
  • +
+

function 中暴露了多套注册方法, 根据函数接受的参数个数不同, 分为: register_0_arg, register_1_arg ..

+

另外, 根据不同的功能需求, 我们提供了不同Level的注册API

+ + + + + + +
Auto VectorizationAccess Output Column BuilderAuto Null PassthroughAuto Combine NullAuto DowncastThrow Runtime ErrorVaridicTuple
register_n_arg✔️✔️✔️
register_passthrough_nullable_n_arg✔️✔️✔️✔️
register_combine_nullable_n_arg✔️✔️✔️✔️✔️
register_n_arg_core✔️✔️✔️
register_function_factory✔️✔️✔️✔️
+
    +
  • Domain解析:
  • +
+

Domain是函数的输入的值域经过函数转换后得出的值域, 一些函数计算是符合单调性等特性的, 利用这类特性我们轻量级计算出函数的值域,这对后续的Partition Prune 有很大帮助, 例如: 数据在底层是通过 timestamp 排序的, 在索引层我们会有timestamp列的 Min/Max 索引, 那么对于带 where to_date(timestamp) > '2020-01-01' 过滤条件的SQL查询, 根据索引数据可以利用 Domain 计算出 to_date(timestamp) 列的 Min/Max 索引,从而进入 Prune 逻辑。

+
    +
  • 类型自动转换规则:
  • +
+
    +
  1. 数值类型在精度不丢失的情况下能自动向上转型,如 i8 --> i16 --> i32 --> i64
  2. +
  3. 所有 integer 类型能转为 int64 类型, 如 u32 --> i64, u64 ---> i64, 转型过程出现溢出会抛出错误
  4. +
  5. 所有数值类型能转为 float64 类型, 如 i32 --> f64, u64 --> f64, 转型过程出现溢出会抛出错误
  6. +
  7. null类型能转为nullable<T>类型, 如: null --> nullable<i32>
  8. +
  9. T 类型能转为 nullable<T> 类型, 如: i32 --> nullable<i32>
  10. +
  11. 嵌套规则: 如果 T 能转为 U 类型,则 nullable<T> 类型能转为 nullable<U> 类型, 则 Array<T> 类型能转为 Array<U> 类型
  12. +
+

由于数值类型较多,大部分情况下我们只需要定义 最大类型即可,如 asin 只需要定义 NumberType<f64> 类型的参数, 接收到其他类型的参数时会自动转型, 例如: asin(i32) 会自动转型为 asin(f64); 在少数性能敏感计算,我们会给较小范围的 数值参数定义额外函数重载, 如 plus, minus, 此时由于自动转换规则的存在,我们必须注意重载的函数必须定义在最大类型的函数之前, 因为函数的查找是按注册顺序进行查找, 只捕获符合条件的第一个函数。

+

所以: i32 优先 定义于 i64, u64 优先定义于 i64, i64 优先定义于 f64, null 优先定义于 nullable

+

Learn More

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/how-to-write-system-table/index.html b/docs/contribute-to-databend/how-to-write-system-table/index.html new file mode 100644 index 0000000..b1181e4 --- /dev/null +++ b/docs/contribute-to-databend/how-to-write-system-table/index.html @@ -0,0 +1,678 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何为 Databend 添加新的系统表 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

如何为 Databend 添加新的系统表

+ +

Databend 的绝大部分系统表都位于 query/storage 这个目录下,当然,如果因为一些特殊的构建原因无法放在这个位置的话,也可以考虑临时放到 service/databases/system 这个目录(不推荐)。

+

系统表的定义主要关注两个内容:一个是表的信息,会包含表名、Schema 这些;另一个就是表中数据的生成/获取。刚好可以对应到 SyncSystemTableAsyncSystemTable 这两个 Trait 中的 get_table_infoget_full_data 。到底是同步还是异步,取决于在获取数据时,是否涉及到异步函数的调用。

+

实现

+

本文将会以 credits 表的实现为例,介绍 Databend 系统表的实现,代码位于 https://github.com/datafuselabs/databend/blob/main/src/query/storages/system/src/credits_table.rs 。credits 会返回 Databend 所用到的上游依赖的信息,包括名字、版本和许可三个字段。

+

首先,需要参考其他系统表的实现,去定义表对应的结构,只需要保有表信息的字段就可以了。

+
pub struct CreditsTable {
+    table_info: TableInfo,
+}
+
+

接下来是为 CreditsTable 表实现 create 方法,对应的函数签名如下:

+
pub fn create(table_id: u64) -> Arc<dyn Table>
+
+

传入的 table_id 会在创建表时由 sys_db_meta.next_table_id() 生成。

+

schema 用于描述表的结构,需要使用 TableSchemaRefExtTableField 来创建,字段名字和类型取决于表中的数据。

+
let schema = TableSchemaRefExt::create(vec![
+    TableField::new("name", TableDataType::String),
+    TableField::new("version", TableDataType::String),
+    TableField::new("license", TableDataType::String),
+]);
+
+

对于字符串类数据,可以使用 TableDataType::String ,其他基础类型也类似。但如果你需要允许字段中存在空值,比如字段是可以为空的 64 位无符号整数,则可以使用 TableDataType::Nullable(Box::new(TableDataType::Number(NumberDataType::UInt64))) 的方式,TableDataType::Nullable 表示允许空值,TableDataType::Number(NumberDataType::UInt64) 表征类型是 64 位无符号整数。

+

接下来就是定义表的信息,基本上只需要依葫芦画瓢,把描述、表名、元数据填上就好。

+
let table_info = TableInfo {
+    desc: "'system'.'credits'".to_string(),
+    name: "credits".to_string(),
+    ident: TableIdent::new(table_id, 0),
+    meta: TableMeta {
+        schema,
+        engine: "SystemCredits".to_string(),
+        ..Default::default()
+    },
+   ..Default::default()
+};
+
+SyncOneBlockSystemTable::create(CreditsTable { table_info })
+
+

对于同步类型的表往往使用 SyncOneBlockSystemTable 创建,异步类型的则使用 AsyncOneBlockSystemTable

+

接下来,则是实现 SyncSystemTableSyncSystemTable 除了需要定义 NAME 之外,还需要实现 4 个函数 get_table_infoget_full_dataget_partitionstruncate ,由于后两个有默认实现,大多数时候不需要考虑实现自己的。(AsyncSystemTable 类似,只是没有 truncate

+

NAME 的值遵循 system.<name> 的格式。

+
const NAME: &'static str = "system.credits";
+
+

get_table_info 只需要返回结构体中的表信息。

+
fn get_table_info(&self) -> &TableInfo {
+    &self.table_info
+}
+
+

get_full_data 是相对重要的部分,因为每个表的逻辑都不太一样,credits 的三个字段基本类似,就只举 license 字段为例。

+
let licenses: Vec<Vec<u8>> = env!("DATABEND_CREDITS_LICENSES")
+    .split_terminator(',')
+    .map(|x| x.trim().as_bytes().to_vec())
+    .collect();
+
+

license 字段的信息是从名为 DATABEND_CREDITS_LICENSES 的环境变量(参见 common-building)获取的,每条数据都用 , 进行分隔。

+

字符串类型的列最后是从 Vec<Vec<u8>> 转化过来,其中字符串需要转化为 Vec<u8> ,所以在迭代的时候使用 .as_bytes().to_vec() 做了处理。

+

在获取所有数据后,就可以按 DataBlock 的形式返回表中的数据。非空类型,使用 from_data ,可空类型使用 from_opt_data

+
Ok(DataBlock::new_from_columns(vec![
+    StringType::from_data(names),
+    StringType::from_data(versions),
+    StringType::from_data(licenses),
+]))
+
+

最后,要想将其集成到 Databend 中,还需要编辑 src/query/service/src/databases/system/system_database.rs,将其注册到 SystemDatabase 中 。

+
impl SystemDatabase {
+    pub fn create(sys_db_meta: &mut InMemoryMetas, config: &Config) -> Self {
+    ...
+        CreditsTable::create(sys_db_meta.next_table_id()),
+    ...
+    }
+}
+
+

测试

+

系统表的相关测试目前仍然位于 src/query/service/tests/it/storages/system.rs

+

对于内容不会经常动态变化的表,可以使用 Golden File 测试,其运行逻辑是将对应的表写入指定的文件中,然后对比每次测试时文件内容是否发生变化。

+
#[tokio::test(flavor = "multi_thread")]
+async fn test_columns_table() -> Result<()> {
+    let (_guard, ctx) = crate::tests::create_query_context().await?;
+
+    let mut mint = Mint::new("tests/it/storages/testdata");
+    let file = &mut mint.new_goldenfile("columns_table.txt").unwrap();
+    let table = ColumnsTable::create(1);
+
+    run_table_tests(file, ctx, table).await?;
+    Ok(())
+}
+
+

对于内容可能会变化的表,目前缺乏充分的测试手段。可以选择测试其中模式相对固定的部分,比如行和列的数目;也可以验证输出中是否包含特定的内容。

+

+#[tokio::test(flavor = "multi_thread")]
+async fn test_metrics_table() -> Result<()> {
+	...
+    let result = stream.try_collect::<Vec<_>>().await?;
+    let block = &result[0];
+    assert_eq!(block.num_columns(), 4);
+    assert!(block.num_rows() >= 1);
+
+    let output = pretty_format_blocks(result.as_slice())?;
+    assert!(output.contains("test_test_metrics_table_count"));
+    #[cfg(feature = "enable_histogram")]
+    assert!(output.contains("test_test_metrics_table_histogram"));
+
+    Ok(())
+}
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/index.html b/docs/contribute-to-databend/index.html new file mode 100644 index 0000000..99f58ad --- /dev/null +++ b/docs/contribute-to-databend/index.html @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +开源贡献 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+ +
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/release/index.html b/docs/contribute-to-databend/release/index.html new file mode 100644 index 0000000..992494d --- /dev/null +++ b/docs/contribute-to-databend/release/index.html @@ -0,0 +1,593 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 版本发布 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + +
+

Databend 版本发布

+ +

版本发布

+

目前 Databend 采用 nightly 发布模式,每天夜里都会打包二进制文件,并递增 patch 版本。

+

targets

+

目前主要关注的 targets 包括:

+
    +
  • x86_64-unknown-linux-gnu
  • +
  • x86_64-unknown-linux-musl
  • +
  • x86_64-apple-darwin
  • +
  • aarch64-unknown-linux-gnu
  • +
  • aarch64-unknown-linux-musl
  • +
  • aarch64-apple-darwin
  • +
+

内容物概要

+

为方便体验,release 中除了 meta 和 query 的二进制文件之外,还包含一份默认配置和用于快速启动的脚本。

+
✦ ❯ tree .
+.
+├── bin
+│   ├── databend-meta
+│   ├── databend-metabench
+│   ├── databend-metactl
+│   └── databend-query
+├── configs
+│   ├── databend-meta.toml
+│   └── databend-query.toml
+├── readme.txt
+└── scripts
+    ├── start.sh
+    └── stop.sh
+
+3 directories, 11 files
+
+

readme.txt 中包含一些必要的提示信息,只需执行 ./scripts/start.sh 即可快速启动 databend 。

+

路线图

+

尽管采用 nightly 发布模式,但 Databend 并非野蛮生长。除了年度路线图外,Databend 还会按开发阶段发布版本路线图,这也决定了当前 minor 版本的分配。

+

年度路线图

+ +

版本路线图

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/routine-maintenance/index.html b/docs/contribute-to-databend/routine-maintenance/index.html new file mode 100644 index 0000000..e53e635 --- /dev/null +++ b/docs/contribute-to-databend/routine-maintenance/index.html @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 的日常维护是怎么进行的 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 的日常维护是怎么进行的

+

日常维护工作虽然简单,但却是保证项目活力和竞争力的有效手段。本文将会介绍 Databend 是如何与最新的工具链/依赖关系协同的。

+

Databend 的日常维护工作主要分为两大类:一类是工具链更新,另一类则是依赖关系更新。

+

工具链更新

+

工具链更新会随着 Rust 版本更新进行。在新的 stable 版本发布后,Databend 就会升级到对应日期附近的 nightly 工具链。

+

必备工作

+

更新工具链的必要步骤是编辑 scripts/setup/rust-toolchain.tomlchannel 字段,并提交 Pull Request 。PR 合并后,会构建新的 build-tool 镜像,以确保 Databend 的 CI workflow 可以正常运行。

+

一般步骤

+

在镜像构建完成后,还需要完成以下工作:

+
    +
  • 编辑 rust-toolchain.toml,确保它和 scripts/setup/rust-toolchain.toml 一致。
  • +
  • 运行 scripts/setup/run_build_tool.sh make lint,确保 clippy 无警告。 +
      +
    • 通常情况下,clippy 会给出一些中肯的建议,但并非完全正确。
    • +
    • 如果有必要,可以使用 #[allow(clippy::xxx)] 来跳过部分 clippy 规则。
    • +
    • 请保留必备的注释以解释为什么需要当漏网之鱼。
    • +
    +
  • +
  • 运行 scripts/setup/run_build_tool.sh make test,确保测试通过。
  • +
+

注意事项

+
    +
  • build-tool 的使用依赖 docker,请确保已经安装并开启 docker 服务。
  • +
+

依赖关系更新

+

依赖关系更新以大约 30 天一次的频率进行,需要在应用上游最新成果和维持项目稳定构建之间进行权衡。

+

当前 Databend 有数以千计的第三方依赖,除了 crates.io 上的依赖外,还有部分源自 github 上的某次提交或者是分叉的上游项目。

+

一般步骤

+

这里列出一套相对普适的更新步骤:

+
    +
  • 运行 cargo upgrade --workspace 以更新来源为 crates.io 的依赖。 +
      +
    • upgrade 子命令依赖 cargo-edit,在使用前需要安装。
    • +
    +
  • +
  • 检查源自 github 的依赖,并根据实际情况进行更新,需要固定到提交对应的 rev 。 +
      +
    • 请尽量避免只引入 versionbranch 字段,这不利于后续更新维护。
    • +
    +
  • +
  • 更新需要单独更新的依赖项,比如:引入代码变更、暗含版本冲突等。 +
      +
    • 如果该依赖项引发大范围的代码变更,请在日常维护工作结束后再进行更新。
    • +
    +
  • +
  • 分别运行 make lintmake test,以确保更新顺利进行。 +
      +
    • 在此过程中可能会遇到一些需要单独更新的依赖项,请将其回退到之前版本,并重新执行这一步。
    • +
    • 请尽量确保流程结束后可以通过所有检查。
    • +
    +
  • +
+

注意事项

+
    +
  • 如果 cargo upgrade --workspace 无法更新依赖,可以尝试先执行一遍 cargo update
  • +
  • 在日常维护过程中,可能需要对 Cargo.lock 做一些手脚,请确保一切检查都可以顺利通过,并在 PR 中进行解释。
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/tracing-in-databend/index.html b/docs/contribute-to-databend/tracing-in-databend/index.html new file mode 100644 index 0000000..b66e418 --- /dev/null +++ b/docs/contribute-to-databend/tracing-in-databend/index.html @@ -0,0 +1,818 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 全链路追踪 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 全链路追踪

+

全链路追踪意味着能够追踪到每一个调用请求的完整调用链路、收集性能数据并反馈异常。Databend 使用 tracing 赋能可观测性,实现全链路追踪。

+

Databend 与 Tracing

+

初步了解 Databend 怎么实现全链路追踪。

+

初识 Tracing

+

Tracing Logo

+

Tracing 是由 Tokio 团队维护的 Rust 应用追踪框架,用来收集结构化的、基于事件的诊断信息。

+

项目地址:https://github.com/tokio-rs/tracing

+

示例:

+
use tracing::{info, Level};
+use tracing_subscriber;
+
+fn main() {
+    let collector = tracing_subscriber::fmt()
+        // filter spans/events with level TRACE or higher.
+        .with_max_level(Level::TRACE)
+        // build but do not install the subscriber.
+        .finish();
+
+    tracing::collect::with_default(collector, || {
+        info!("This will be logged to stdout");
+    });
+    info!("This will _not_ be logged to stdout");
+}
+
+

Databend 中的 Tracing

+

Databend 的 tracing-subscriber 被统一整合在 common/tracing,由 query 和 meta 共用。

+
// Use env RUST_LOG to initialize log if present.
+// Otherwise use the specified level.
+let directives = env::var(EnvFilter::DEFAULT_ENV).unwrap_or_else(|_x| level.to_string());
+let env_filter = EnvFilter::new(directives);
+let subscriber = Registry::default()
+    .with(env_filter)                # 根据环境变量过滤
+    .with(JsonStorageLayer)          # 利用 tracing-bunyan-formatter 格式化为 json
+    .with(stdout_logging_layer)      # 标准输出
+    .with(file_logging_layer)        # 输出到文件,默认位于 `_logs` 目录下
+    .with(jaeger_layer);             # opentelemetry-jaeger
+
+#[cfg(feature = "console")]
+let subscriber = subscriber.with(console_subscriber::spawn()); # tokio console
+
+tracing::subscriber::set_global_default(subscriber)
+    .expect("error setting global tracing subscriber");
+
+

具体到内部的 tracing 记录,大致有两类:

+
    +
  1. +

    普通:与其他 log 方式一样,利用 info!debug! 来收集信息。

    +
    use common_tracing::tracing;
    +
    +tracing::info!("{:?}", conf);
    +tracing::info!("DatabendQuery {}", *databend_query::DATABEND_COMMIT_VERSION);
    +
    +
  2. +
  3. +

    Instruments:在调用函数时创建并进入 tracing span(跨度),span 表示程序在特定上下文中执行的时间段。

    +
    use common_tracing::tracing::debug_span;
    +#[tracing::instrument(level = "debug", skip_all)]
    +async fn read(&mut self) -> Result<Option<DataBlock>> {
    +    ...
    +        fetched_metadata = read_metadata_async(&mut self.reader)
    +            .instrument(debug_span!("parquet_source_read_meta"))
    +            .await
    +            .map_err(|e| ErrorCode::ParquetError(e.to_string()))?;
    +    ...
    +}
    +
    +
  4. +
+

示例:

+
{
+  "v": 0,
+  "name": "databend-query-test_cluster@0.0.0.0:3307",
+  "msg": "Shutdown server.",
+  "level": 30,
+  "hostname": "dataslime",
+  "pid": 53341,
+  "time": "2022-05-11T00:51:56.374807359Z",
+  "target": "databend_query",
+  "line": 153,
+  "file": "query/src/bin/databend-query.rs"
+}
+
+

观测 Databend 追踪的方式

+

Databend 原生提供了多种观测方式,以方便诊断和调试:

+
    +
  1. http tracing :访问 localhost:8080/v1/logs(根据配置)。
  2. +
  3. stdout/filelog :检查终端输出或 _logs 目录(根据配置)。
  4. +
  5. system.tracing 表 :执行 select * from system.tracing limit 20;
  6. +
  7. jaeger :运行 jaeger ,访问 http://127.0.0.1:16686/ 。
  8. +
  9. console :按特定方式构建后,运行 tokio-console 。
  10. +
+

Jaeger 分布式追踪

+

使用 Jaeger 对 Databend 进行全链路追踪。

+

Opentelemetry & Jaeger

+

OpenTelemetry 是工具、API 和 SDK 的集合。使用它来检测、生成、收集和导出遥测数据(度量、日志和追踪),以帮助您分析软件的性能和行为。

+

Jaeger 是一个开源的端到端分布式追踪系统。由 Uber 捐赠给 CNCF 。它可以用于监视基于微服务的分布式系统,提供以下能力:

+
    +
  • 分布式上下文传播
  • +
  • 分布式事务监视
  • +
  • 根本原因分析
  • +
  • 服务依赖性分析
  • +
  • 性能/延迟优化
  • +
+

Opentelemetry & Jaeger

+

Step by Step

+

遵循下述步骤,即可使用 Jaeger 探索 Databend :

+
    +
  1. 构建二进制程式:cargo build(可以使用 --bin 指定)。
  2. +
  3. 指定 Jaeger endpoint ,并将日志级别设定为 DEBUG ,接着运行需要调试的应用程式。例如,DATABEND_JAEGER_AGENT_ENDPOINT=localhost:6831 LOG_LEVEL=DEBUG ./databend-query
  4. +
  5. 运行 jaeger :docker run -d -p6831:6831/udp -p6832:6832/udp -p16686:16686 jaegertracing/all-in- one:latest
  6. +
  7. 打开 http://127.0.0.1:16686/ 以查看 jaeger 收集的信息。
  8. +
+

注意 只有正确配置 DATABEND_JAEGER_AGENT_ENDPOINT 才能启用 Jaeger 支持。

+

结果探索

+

dot graph

+

x 轴是执行时刻,y 轴是持续的时间,圆点反映 span 的聚集程度。

+

执行下述语句即可得到上图所示追踪结果:

+
CREATE TABLE t1(a INT);
+INSERT INTO t1 VALUES(1);
+INSERT INTO t1 SELECT * FROM t1;
+
+

Timeline

+

下图是点击最大的圆点得到的追踪情况:

+

span tracing

+

使用 timeline 模式来展现 tracing 的各个跨度之间的关系。以时间为主线进行分析,方便使用者观看在某个时间点观看程序信息。

+

点开第一个跨度,可以看到这是执行 INSERT INTO t1 SELECT *FROM t1 查询时的情况。

+

span info

+

Graph

+

切换到 graph 模式,可以看到各个 span 之间的调用链,每个 span 具体用时 ,以及百分比。

+

span graph

+

通过这个视图使用者很容易知道系统瓶颈,快速定位问题。

+

Compare

+

连起来的各个部分形成整个 trace 的调用链。因为比较时一般会比较两个相同类型的调用,所以看到的会是重合后的视图。

+

span compare

+

对于颜色的一个说明:

+
    +
  • 深绿色,表示这个 span 只存在于 trace-B 中,A 没有这个 span
  • +
  • 深红色,表示这个 span 只存在于 trace-A 中,B 没有这个 span
  • +
  • 浅绿色,表示这个 span 在 trace-B(右边这个)的数量多
  • +
  • 浅红色,表示这个 span 在 trace-A(左边这个)的数量多
  • +
+

tokio-console 诊断

+

tokio-rs 团队出品的诊断和调试工具,可以帮助我们诊断与 tokio 运行时相关的问题。

+

console 是什么

+

tokio console

+

tokio-console 是专为异步程序设计的调试与诊断工具,能够列出 tokio 的任务,提供对程序的任务和资源的实时、易于导航的视图,总结了它们的当前状态和历史行为。主要包含以下组件:

+
    +
  • 传输诊断数据的协议
  • +
  • 用于收集诊断数据的仪器
  • +
  • 当然,用于展示和探索诊断数据的实用工具
  • +
+

项目地址:https://github.com/tokio-rs/console

+

Step by Step

+
    +
  1. 使用特定的 rustflagsfeatures 来构建: +RUSTFLAGS="--cfg tokio_unstable" cargo build --features tokio-console ,也可以只构建单个二进制程式,使用 --bin 进行指定。
  2. +
  3. 将日志级别设定为 TRACE ,运行需要调试的应用程式 LOG_LEVEL=TRACE databend-query 或者 databend-meta --single --log-level=TRACE。可以使用 TOKIO_CONSOLE_BIND 指定端口,以避免潜在的端口抢占问题。
  4. +
  5. 运行 tokio-console,默认连接到 http://127.0.0.1:6669 。
  6. +
+

结果探索

+

任务

+

先看什么是 tokio 任务:

+
    +
  1. 任务是一个轻量级的、非阻塞的执行单元。类似操作系统的线程,但是是由 tokio 运行时管理,一般叫做“绿色线程”,与 Go 的 goroutine,Kotlin 的 coroutine 类似。
  2. +
  3. 任务是协同调度的。大多数操作系统实现抢占式多任务。操作系统允许每个线程运行一段时间,然后抢占它,暂停该线程并切换到另一个线程。另一方面,任务实现协同多任务。一个任务被允许运行直到它让出执行权,运行时会切换到执行下一个任务。
  4. +
+

tokio console basic

+

基础视图

+

通过左右切换,可以得到总忙时间或轮询次数等指标对任务进行排序。控制台通过高亮来提示较大差异,比如从毫秒到秒的切换。

+

tokio console sort

+

控制台还实现了一个“警告”系统。通过监视应用程序中任务的运行时操作,控制台可以检测可能提示 bug 或性能问题的行为模式,并突出显示这些行为模式供用户分析。比如已经运行了很长时间而没有让步的任务,唤醒的次数比被其他任务唤醒的次数还要多的任务。

+

任务视图

+

上下切换选中任务,enter 查看关于每个任务的翔实数据,比如轮询持续时间的直方图。

+

tokio console task

+

不仅列出任务。console 还会插入异步互斥和信号量等资源。Tokio Console 的资源详细信息视图显示了哪些任务已经进入临界区,哪些任务正在等待获得访问权限。

+

还能做什么

+

与分布式追踪和日志系统相关的一些其他内容。

+

可观测性 + +

+

目前还有一系列关于可观测性和 Tracing 的 Issue 有待解决:

+
    +
  • Integrate tokio metrics for query task based tokio runtime monitoring #4205
  • +
  • Configure on jaeger tracing address similar to metrics api server #3633
  • +
  • Summary of todos about distributed tracing #1227
  • +
  • Query traces and analysis, based on user behavior. #1177
  • +
  • Http stack traces #1085
  • +
  • Shuffle read/write metrics #1004
  • +
+

另外,更进一步的考量是,如何基于可观测性来自动/半自动地发现问题并对系统进行调优。

+

了解更多 . . .

+

Tracing

+
    +
  • https://github.com/tokio-rs/tracing
  • +
  • https://docs.rs/tracing/latest/tracing/
  • +
  • https://tokio.rs/blog/2019-08-tracing
  • +
+

Jaeger

+
    +
  • https://github.com/open-telemetry/opentelemetry-rust/tree/main/opentelemetry-jaeger
  • +
  • https://21-lessons.com/ship-rust-tracing-spans-to-jaeger/
  • +
+

tokio-console

+
    +
  • https://github.com/tokio-rs/console
  • +
  • https://hackmd.io/@aws-rust-platform/ByChcdB-t
  • +
  • https://tokio.rs/blog/2021-12-announcing-tokio-console
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contribute-to-databend/write-and-run-tests/index.html b/docs/contribute-to-databend/write-and-run-tests/index.html new file mode 100644 index 0000000..0ee4d8a --- /dev/null +++ b/docs/contribute-to-databend/write-and-run-tests/index.html @@ -0,0 +1,980 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何为 Databend 添加新的测试 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

如何为 Databend 添加新的测试

+

测试是提高软件健壮性、加速迭代进程的不二法宝。本文将会介绍如何为 Databend 添加不同种类的测试。

+
+

在「产品力:Databend 的质量保障」一文中,已经介绍到组成 Databend 测试的两个重要部分 —— 单元测试和功能测试。如有遗忘,不妨回顾一下。

+
+

单元测试

+

Databend 的单元测试组织形式有别于一般的 Rust 项目,是直接一股脑放在 tests/it 目录下的。同时,在各个 crate 的 Cargo.toml 中,也针对性地禁用了 doctestbin/lib test

+

优点

+
    +
  • 减少需要构建的测试目标,提高测试编译/链接速度。
  • +
  • 当需要添加新单元测试时(不修改 src),只需要编译对应的 it(test) ,节省时间。
  • +
+

缺点

+
    +
  • tests/it 会把需要测试的 crate 当作一个外部对象,所有待测试的内容都需要被设定为 pub 。不利于软件设计上的分层,整个项目结构会迅速的被破坏,需要引入编码规范并更加依赖开发者的主动维护。
  • +
+

编写

+

可以简单地将单元测试分为两类,一类是不需要外部文件介入的纯 Rust 测试,一类是 Golden Files 测试。

+

Rust 测试

+

与平时编写 Rust 单元测试相同,只是待测试的内容需要设为 pub ,且引用待测试 crate 需要使用该 crate 的名字。

+

Databend 提供一些用于模拟全局状态的函数,如 create_query_context 等,可能会有助于编写测试。

+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
+async fn test_credits_table() -> Result<()> {
+    let ctx = crate::tests::create_query_context().await?;
+
+    let table = CreditsTable::create(1);
+    let source_plan = table.read_plan(ctx.clone(), None).await?;
+
+    let stream = table.read(ctx, &source_plan).await?;
+    let result = stream.try_collect::<Vec<_>>().await?;
+    let block = &result[0];
+    assert_eq!(block.num_columns(), 3);
+    Ok(())
+}
+
+

上面示例来自 credits_table 的测试,先构建 read_plan 读取新建的 CreditsTable 表,再对列数进行断言。

+

Golden Files 测试

+
+

Golden File Testing are like unit tests, except the expected output is stored in a separate file. -- Max Grigorev at ZuriHac

+
+

Golden Files 测试是一种常用的测试手段,相当于是一类快照测试,如果执行情况和预期结果存在差异则认为测试失败。

+

Databend 使用 goldenfile 这个 crate 来编写 Golden Files 测试。目前 Databend 有计划用此替代 assert_blocks 系列断言。

+
#[test]
+fn test_expr_error() {
+    let mut mint = Mint::new("tests/it/testdata");
+    let mut file = mint.new_goldenfile("expr-error.txt").unwrap();
+
+    let cases = &[
+        r#"5 * (a and ) 1"#,
+        r#"a + +"#,
+        r#"CAST(col1 AS foo)"#,
+        r#"1 a"#,
+        r#"CAST(col1)"#,
+        r#"G.E.B IS NOT NULL AND
+            col1 NOT BETWEEN col2 AND
+                AND 1 + col3 DIV sum(col4)"#,
+    ];
+
+    for case in cases {
+        run_parser!(file, expr, case);
+    }
+}
+
+

编写 Golden Files 测试时需要指定挂载的目录和对应预期结果的文件。

+

在执行测试的主体部分(如上面示例中的 run_parser! 宏),除了封装运行测试的必要逻辑外,还需要定义输出时的格式。

+

测试文件必须按指定格式编写。或者,使用 REGENERATE_GOLDENFILES=1 生成。

+

下面 Golden File 的例子节选自 common/ast 模块测试的 testdata/expr-error.txtOutput 对应解析 5 * (a and ) 1 的预期结果。

+
---------- Input ----------
+5 * (a and ) 1
+---------- Output ---------
+error: 
+  --> SQL:1:12
+  |
+1 | 5 * (a and ) 1
+  | -          ^ expected more tokens for expression
+  | |           
+  | while parsing expression
+
+

运行

+

单元测试的运行可以运行 make unit-test 或者是 cargo test --workspace

+

二者的区别在于 make unit-test 封装了 ulimit 命令控制最大文件数和栈的大小以确保测试能够顺利运行,如果使用 MacOS 则更建议使用 make unit-test

+

通过过滤机制,可以轻松指定运行名字中具有特定内容的测试,例如 cargo test test_expr_error

+

排查

+

Rust 测试

+

同其他项目中的 Rust 测试一样,可以根据友好的错误提示轻松定位出现故障的测试。如果需要详细的 Backtrace ,可以在运行测试命令时添加环境变量 RUST_BACKTRACE=1

+
failures:
+
+---- buffer::buffer_read_number_ext::test_read_number_ext stdout ----
+Error: Code: 1046, displayText = Cannot parse value:[] to number type, cause: lexical parse error: 'the string to parse was empty' at index 0.
+
+<Backtrace disabled by default. Please use RUST_BACKTRACE=1 to enable> 
+thread 'buffer::buffer_read_number_ext::test_read_number_ext' panicked at 'assertion failed: `(left == right)`
+  left: `1`,
+ right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', /rustc/cd282d7f75da9080fda0f1740a729516e7fbec68/library/test/src/lib.rs:185:5
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+
+

Golden Files 测试

+

Golden Files 测试的执行命令与 Rust 测试相同,但在错误提示方面有所差异。得益于 goldenfiles 引入了 similar-assert ,可以轻松识别 diff :

+
Differences (-left|+right):
+ ---------- Output ---------
+ 'I'm who I'm.'
+ ---------- AST ------------
+ Literal {
+     span: [
+-        QuotedString(0..18),
++        QuotedString(0..16),
+     ],
+     lit: String(
+         "I'm who I'm.",
+     ),
+ }
+.cargo/git/checkouts/rust-goldenfile-6352648ef139d984/16c5783/src/differs.rs:15:5
+note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
+
+

上面示例中,+ 对应测试实际结果,- 对应测试预期结果,其他为相关的上下文。

+

goldenfiles 的报错可能会涉及多个测试文件,受限于长文本支持和空格显示,排查仍可能存在不便。

+

这里提供一个相对友好的排查思路:

+
    +
  1. 确保之前的更改都已经提交,然后运行 REGENERATE_GOLDENFILES=1 cargo test -p <package> --test it 重新生成对应的测试。
  2. +
  3. 执行 git diff 来显示前后 goldenfiles 文件的差异。
  4. +
  5. 仔细辨别问题出现原因,确定是否存在预期外的问题。
  6. +
+

功能测试

+

功能测试主要由 SQL 逻辑测试(sqllogictest)和 stateful 测试两个部分组成。

+

从本质上讲,这两类功能测试流程相同:

+
    +
  • 启动 databend 实例。
  • +
  • 使用对应的客户端/驱动执行查询。
  • +
  • 对比查询情况和预期行为之间的差异,判断测试是否通过。
  • +
+

在设计上,SQL 逻辑测试可以提供更全面的能力:

+
    +
  • 拓展比较结果文件的方式到其他协议(涵盖 http handler)。
  • +
  • 提示每个语句的结果。
  • +
  • 提供错误处理的能力。
  • +
  • 支持排序、重试等测试逻辑。
  • +
+

编写

+

SQL 逻辑测试

+

SQL 逻辑测试放在 tests/logictest 目录下。

+

语句规范在 sqlite sqllogictest 的基础上进行拓展,可以分成以下几类:

+
    +
  • statement ok :SQL 语句正确,且成功执行。
  • +
  • statement error <error regex> :SQL 语句输出期望的错误。
  • +
  • statement query <desired_query_schema_type> <options> <labels> :SQL语句成功执行并输出预期结果。
  • +
+
statement query B label(mysql,http)
+select count(1) > 1 from information_schema.columns;
+
+----  mysql
+1
+
+----  http
+true
+
+

上面的例子展示了如何对 mysql 和 http 分别设计对应的输出结果。其中 B 表示结果为布尔类型,label 用来标记协议。

+

SQL 逻辑测试同样支持测试集生成 python3 gen_suites.py

+

stateful 测试

+

stateful 测试放在 tests/suites 目录下:

+
+

这里展示的是一类 stateless 写法,stateful 与之类似,区别在于 stateful 会加载数据集并执行查询。

+
+
    +
  • +

    输入是一系列 sql 语句,对应目录中的 *.sql 文件。

    +
    SELECT '==Array(Int32)==';
    +
    +CREATE TABLE IF NOT EXISTS t2(id Int null, arr Array(Int32) null) Engine = Fuse;
    +
    +INSERT INTO t2 VALUES(1, [1,2,3]);
    +INSERT INTO t2 VALUES(2, [1,2,4]);
    +INSERT INTO t2 VALUES(3, [3,4,5]);
    +SELECT max(arr), min(arr) FROM t2;
    +SELECT arg_max(id, arr), arg_min(id, arr) FROM (SELECT id, arr FROM t2);
    +
    +
  • +
  • +

    输出对应查询结果(含报错),如果没有输出则需要置空,对应目录中的 *.result 文件。

    +
    ==Array(Int32)==
    +[3, 4, 5]	[1, 2, 3]
    +3	1
    +
    +
  • +
+

测试可以覆盖 SQL 执行过程中遇到预期错误的情况,有两种方式:

+
    +
  • +

    沿用上面的方法,在 result 文件中标注具体报错信息。

    +
  • +
  • +

    也可以采用 ErrorCode 注释的方式,此时无需在 result 文件中添加对应内容。

    +
    SELECT INET_ATON('hello');-- {ErrorCode 1060}
    +
    +
  • +
+

运行

+
+

由于 stateful 测试和 sqllogictest 测试均由 Python 编写,在运行前请确保你已经安装全部的依赖。

+
+

这几类测试都有对应的 make 命令,并支持集群模式测试:

+
    +
  • sqllogictest 测试:make sqllogic-test & make sqllogic-cluster-test
  • +
  • stateful 测试:make stateful-test & make stateful-cluster-test 。(一般在 CI 中运行,本地需要正确配置 MINIO 环境)。
  • +
+

排查

+

sqllogictest 测试

+

sqllogictest 测试能提供精准到语句的报错,并提供更多有效的上下文帮助排查问题。

+
AssertionError: Expected:
+INFORMATION_SCHEMA
+default
+ Actual:
+  INFORMATION_SCHEMA
+          db_12_0003
+             default
+ Statement:
+Parsed Statement
+    at_line: 77,
+    s_type: Statement: query, type: T, query_type: T, retry: False,
+    suite_name: gen/02_function/02_0005_function_compare,
+    text:
+        select * from system.databases where name not like '%sys%' order by name;
+    results: [(<re.Match object; span=(0, 4), match='----'>, 83, 'INFORMATION_SCHEMA\ndefault')],
+    runs_on: {'mysql', 'clickhouse', 'http'},
+ Start Line: 83, Result Label: 
+make: *** [Makefile:82: sqllogic-test] Error 1
+
+

stateful 测试

+

目前 stateful 测试能够提供文件级的报错和 diff ,但无法确定报错是由哪一条语句产生。

+
+

这里展示的是过去 stateless 引发的报错,stateful 与之类似。

+
+
02_0057_function_nullif:                                                [ FAIL ] - result differs with:
+--- /projects/datafuselabs/databend/tests/suites/0_stateless/02_function/02_0057_function_nullif.result
++++ /projects/datafuselabs/databend/tests/suites/0_stateless/02_function/02_0057_function_nullif.stdout
+@@ -3,7 +3,7 @@
+ 1
+ 1
+ NULL
+-a
++b
+ b
+ a
+ NULL
+
+Having 1 errors! 207 tests passed.                     0 tests skipped.
+The failure tests:
+    /projects/datafuselabs/databend/tests/suites/0_stateless/02_function/02_0057_function_nullif.sql
+
+

提示

+
    +
  • 移除 databend-query-standalone-embedded-meta.sh 等脚本中的 nohup 有助于在测试时同时输出日志到终端,可能同样有助于排查。
  • +
  • stateful 超时类错误(Timeout!)的默认时间限制为 10 分钟。为方便排查,可以将 databend-test 文件中的 timeout 改短。
  • +
+

SQLacner for Databend 测试

+

一、运行

+

1、启动databend-query并创建用户

+
./databend/bin/databend-query &
+mysql -uroot -h127.0.0.1 -P3307 -e "CREATE USER 'sqlancer' IDENTIFIED BY 'sqlancer'; GRANT ALL ON *.* TO sqlancer;"
+
+

2、打包sqlancer

+
git clone https://github.com/sqlancer/sqlancer.git
+cd sqlancer
+mvn package -DskipTests
+
+

3、运行sqlancer

+
#方式一,运行test单元
+DATABEND_AVAILABLE=true mvn -Dtest=TestDatabend test
+#方式二,运行jar包,可指定参数
+cd target
+java -jar sqlancer-*.jar --num-threads 4 --random-string-generation ALPHANUMERIC databend --oracle WHERE
+#运行成功每5s输出一条信息
+[2022/10/01 19:33:12] Executed 1037 queries (207 queries/s; 0.80/s dbs, successful statements: 100%). Threads shut down: 0.
+[2022/10/01 19:33:17] Executed 2133 queries (219 queries/s; 0.00/s dbs, successful statements: 100%). Threads shut down: 0.
+[2022/10/01 19:33:22] Executed 3248 queries (223 queries/s; 0.00/s dbs, successful statements: 100%). Threads shut down: 0.
+[2022/10/01 19:33:27] Executed 4351 queries (220 queries/s; 0.00/s dbs, successful statements: 100%). Threads shut down: 0.
+
+

4、列举以下常用options(主可选项定义在MainOptions类,另外一个定义在DatabendOptions类)

+

-h:查看帮助

+

--num-threads:线程数

+

--timeout-seconds:运行时间,单位秒,默认-1无限执行

+

--num-tries:指定多少次异常后停止测试,默认100

+

--database-prefix:数据库的前缀名

+

--random-string-generation:随机字符串模式

+

--host:主机,默认localhost

+

--port:端口,默认3307

+

--username:用户名,默认sqlancer

+

--password:密码,默认sqlancer

+

databend:指定的DBMS

+

--oracle:测试的方式

+

Non-optimizing Reference Engine Construction (NoREC)

+
    +
  • NoREC
  • +
+

Ternary Logic Partitioning (TLP)

+
    +
  • QUERY_PARTITIONING
  • +
  • WHERE
  • +
  • GROUP_BY
  • +
  • HAVING
  • +
  • DISTINCT
  • +
  • AGGREGATE
  • +
+

二、排查

+

当sqlancer停止测试后,会先清除原来的日志文件(注意备份),然后将所有错误写入到新的日志文件中 ./sqlancer/logs/databend/*.log 其中 *-cur.log 为某个db执行的sql记录。

+

1、TLP检测出logic bug

+

若TLP检测出logic bug,日志文件会包含以下信息(错误的 query sql与重现db的sql):其中cardinality不一致说明发生了logic bug。

+
--java.lang.AssertionError: the size of the result sets mismatch (3 and 6)!
+-- Time: 2022/08/29 16:57:50
+-- Database: databend1
+-- Database version: 8.0.26-v0.8.12-nightly-74d0287-simd(rust-1.64.0-nightly-2022-08-27T03:09:50.519081067Z)
+-- seed value: 1
+DROP DATABASE IF EXISTS databend1;
+CREATE DATABASE databend1;
+USE databend1;
+CREATE TABLE t0(c0BOOLEAN BOOLEAN NULL DEFAULT(true));
+CREATE TABLE t1(c0FLOAT DOUBLE NULL DEFAULT(0.8522535562515259), c1VARCHAR VARCHAR NULL);
+INSERT INTO t1(c0float) VALUES (0.48751503229141235);
+INSERT INTO t1(c0float, c1varchar) VALUES (0.8522535562515259, NULL);
+INSERT INTO t1(c1varchar) VALUES ('2'), ('78');
+INSERT INTO t0(c0boolean) VALUES (false), (false), (true);
+INSERT INTO t1(c0float) VALUES (0.8522535562515259), (0.48751503229141235);
+INSERT INTO t1(c0float, c1varchar) VALUES (0.48751503229141235, '7555834'), (0.7298239469528198, '8');
+-- SELECT t0.c0boolean FROM t0;
+-- cardinality: 3
+-- SELECT t0.c0boolean FROM t0 WHERE (NULL BETWEEN NULL AND NULL) UNION ALL SELECT t0.c0boolean FROM t0 WHERE (NOT (NULL BETWEEN NULL AND NULL)) UNION ALL SELECT t0.c0boolean FROM t0 WHERE (((NULL BETWEEN NULL AND NULL)) IS NULL);
+-- cardinality: 6
+
+

TLP发现的bug例如:sqlancer: expression expansion error · Issue #7360 · datafuselabs/databend (github.com)

+

2、NoREC检测出logic bug

+

若NoREC检测出logic bug,日志文件会包含以下信息(错误的query sql与重现db的sql):

+

第一条 query sql 返回的是结果的row数,第二条 query sql 返回的是count的和,若两数不一致则出现logic bug。

+
java.lang.AssertionError: SELECT t1.c0float, t1.c1varchar, t0.c0boolean FROM t1 RIGHT  JOIN t0 ON true WHERE (NOT -1257754687); -- 3
+SELECT SUM(count) FROM (SELECT (((NOT -1257754687) IS NOT NULL AND (NOT -1257754687)) ::BIGINT)as count FROM t1 RIGHT JOIN t0 ON true) as res -- 0
+-- Time: 2022/09/24 09:27:50
+-- Database: databend1
+-- Database version: 8.0.26-v0.8.46-nightly-f524701-simd(rust-1.66.0-nightly-2022-09-23T16:20:13.611527635Z)
+-- seed value: 1
+DROP DATABASE IF EXISTS databend1;
+CREATE DATABASE databend1;
+USE databend1;
+CREATE TABLE t0(c0BOOLEAN BOOLEAN NULL DEFAULT(true));
+CREATE TABLE t1(c0FLOAT DOUBLE NULL DEFAULT(0.8522535562515259), c1VARCHAR VARCHAR NULL);
+INSERT INTO t1(c0float) VALUES (0.48751503229141235);
+
+

NoREC发现的bug例如:bug: select error · Issue #7863 · datafuselabs/databend (github.com)

+

3、NoREC与TLP检测出其他Bug

+

NoREC与TLP测试期间还会检测出使得databend错乱的bug:

+

如果报错信息有 Cause by 且报错信息很复杂则极有可能是bug,也可能是sqlancer生成的sql语法错误。

+

情况比较多,例如:

+ +

4、据不完全统计目前测出的Bug

+

Can't construct type from Float32(Float32) and Int64(Int64) · Discussion #7162 · datafuselabs/databend (github.com)

+

long SQL makes parser work really slow. · Issue #7225 · datafuselabs/databend (github.com)

+

bug: const types and nullable types are orthogonal · Issue #7241 · datafuselabs/databend (github.com)

+

Bug in numerical_coercion of in operator · Issue #7203 · datafuselabs/databend (github.com)

+

sqlancer: expression expansion error · Issue #7360 · datafuselabs/databend (github.com)

+

bug: Code: 4000, displayText = block pruning failure, task 1050895 panicked. · Issue #7366 · datafuselabs/databend (github.com)

+

bug: where clause error · Issue #7457 · datafuselabs/databend (github.com)

+

bug: expression evaluation error · Issue #7460 · datafuselabs/databend (github.com)

+

bug: Code: 4000, displayText = unexpected end of file (failed to fill whole buffer) (while in processor thread 15). · Issue #7461 · datafuselabs/databend (github.com)

+

bug: expression evaluation error · Issue #7460 · datafuselabs/databend (github.com)

+

bug: the content of the result sets mismatch · Issue #7463 · datafuselabs/databend (github.com)

+

bug: Code: 4000, displayText = block pruning failure, task 4274595 panicked. · Issue #7464 · datafuselabs/databend (github.com)

+

bug: Code: 1058, displayText = downcast column error · Issue #7483 · datafuselabs/databend (github.com)

+

bug: Hash table capacity overflow · Issue #7495 · datafuselabs/databend (github.com)

+

bug: cannot convert NULL to a non-nullable type · Issue #7498 · datafuselabs/databend (github.com)

+

bug: expression explain error · Issue #7543 · datafuselabs/databend (github.com)

+

bug: select view error · Issue #7573 · datafuselabs/databend (github.com)

+

bug: select error · Issue #7863 · datafuselabs/databend (github.com)

+

bug: where clause explain error · Issue #7864 · datafuselabs/databend (github.com)

+

bug: ERROR 2013 (HY000): Lost connection to server during query · Issue #7949 · datafuselabs/databend (github.com)

+

bug: return error after adding form and join · Issue #8000 · datafuselabs/databend (github.com)

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 第二弹 - Data Source +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 推荐阅读的论文清单 → +
+
+
+ + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contributing/code-of-conduct/index.html b/docs/contributing/code-of-conduct/index.html new file mode 100644 index 0000000..07afc9d --- /dev/null +++ b/docs/contributing/code-of-conduct/index.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +行为准则 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + +
+

行为准则

+ +

我们的承诺

+

身为项目维护者和贡献者,我们承诺使社区参与者不受骚扰,无论其年龄、体型、可见或不可见的缺陷、族裔、性征、性别认同和表达、经验水平、教育程度、社会与经济地位、国籍、相貌、种族、种姓、肤色、宗教信仰、性倾向或性取向如何。

+

我们承诺以有助于建立开放、友善、多样化、包容、健康社区的方式行事和互动。

+

我们的准则

+

有助于创造积极和谐环境的例子包括:

+
    +
  • 使用欢迎和包容的语言。
  • +
  • 尊重不同的观点和经验。
  • +
  • 优雅的接受建设性批评。
  • +
  • 致力于关注社区的整体利益。
  • +
  • 与其他社区成员产生共鸣。
  • +
+

不可接受的参与者行为包括:

+
    +
  • 使用污言秽语。
  • +
  • 恶意诽谤、侮辱或贬损的言论,人身攻击或政治攻击。
  • +
  • 公开或私下的骚扰。
  • +
  • 在没有明确允许下发布他人的私人信息,如住址或电子邮件。
  • +
  • 不符合专业语境的其它行为。
  • +
+

我们的责任

+

项目的维护者有责任解释和落实我们所认可的行为准则,并妥善公正地对他们认为不当、威胁、冒犯或有害的任何行为采取纠正措施。

+

项目的维护者有权力和责任删除、编辑或拒绝或拒绝与本行为准则不相符的评论(comment)、提交(commits)、代码、维基(wiki)编辑、议题(issues)或其他贡献,并在适当时机知采取措施的理由。

+

适用范围

+

本守则适用于任何个人代表此项目或此项目社区时的项目空间和公共空间。项目代表将由项目维护者进一步定义和阐述。

+

如遇到辱骂、骚扰或其它不被接受的行为,请联系 psiace@datafuselabs.com 上报项目团队。所有投诉都将被审核调查,并给出核定后的结果。具体措施的细节可能会单独发布。

+

违背行为守则的项目维护者, 在其他的项目维护者决定下,可能面临临时或永久的惩罚。

+

致谢

+

本行为准则改编自 Contributor Covenant 2.1 版, 参见 https://www.contributor-covenant.org/version/2/1/code_of_conduct.html

+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ ← 推荐阅读的论文清单 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contributing/how-to-contributing/index.html b/docs/contributing/how-to-contributing/index.html new file mode 100644 index 0000000..d0d29ef --- /dev/null +++ b/docs/contributing/how-to-contributing/index.html @@ -0,0 +1,571 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +如何协作 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + +
+

如何协作

+ +

基本协作流程

+

这里列出一些协作相关的基本流程。

+

文章写作

+
+

适用于已经列在大纲下面的文章/主题。

+
+
    +
  1. 规划中 列表中挑选感兴趣的主题,打开 issue ,请求分配给自己。
  2. +
  3. PsiACE 讨论关于主题的想法,并做确认。
  4. +
  5. 写作完成后,新建 Pull Request 并等待 Review 。
  6. +
  7. 当修改完成确认无误后,文章合并。
  8. +
+

添加新主题

+
+

适用于已规划的大纲中不涉及的文章/主题。

+
+
    +
  1. 打开 issue ,描述需求,补充相关的信息和范例。
  2. +
  3. PsiACE 讨论关于主题的想法,并做确认。
  4. +
  5. 确认可行性后,编辑大纲并 新建 Pull Request
  6. +
  7. 合并。
  8. +
+

错误反馈 / 改进建议

+

欢迎 打开 issue 新建 Pull Request 帮助改进内容和版面。

+

在反馈/修改时,请提供相关上下文以帮助判断问题现状。

+

一般讨论

+

本站使用 https://giscus.app/ 关联 Github Discussions

+

文章相关的讨论建议在对应文章的评论区中进行。如有一般性话题,请在 Github Discussions 中选择合适的板块发布。

+

Review 支持

+

可以向以下 Databend 贡献者寻求 Review 支持:

+ + + + + + + +
分类GitHub
Metadrmingdrmer
Querysundy-li
Optimizerleiysky
Clusterzhang2014
Storagedantengsky
Xuanwo
CommonPsiACE
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ ← 推荐阅读的论文清单 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contributing/index.html b/docs/contributing/index.html new file mode 100644 index 0000000..3ec9889 --- /dev/null +++ b/docs/contributing/index.html @@ -0,0 +1,254 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +贡献相关 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/contributing/outline/index.html b/docs/contributing/outline/index.html new file mode 100644 index 0000000..db1e646 --- /dev/null +++ b/docs/contributing/outline/index.html @@ -0,0 +1,605 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +大纲速览 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + +
+

大纲速览

+ +

基础导览

+

已上线

+
    +
  • 查询执行
  • +
+

进行中

+
    +
  • 索引
  • +
  • 存储
  • +
  • 查询优化
  • +
+

规划中

+
    +
  • MPP(大规模并行处理)
  • +
  • 分布式
  • +
+

特性探索

+

需调整

+
    +
  • 使用 Rust 构建云原生数仓 Databend -> 面向云时代的数仓设计
  • +
  • Databend 的质量保障 -> 云原生数据库的质量保障
  • +
+

规划中

+
    +
  • 半结构化数据支持
  • +
  • 分布式与共识
  • +
  • 如何更简单对接存储系统
  • +
  • 各种案例和方案
  • +
+

源码解读

+

已上线

+
    +
  • Databend 源码阅读: 开篇
  • +
  • Databend 源码阅读: Query Server 启动、Session 管理及请求处理
  • +
+

规划中

+

预计以视频和文字稿形式发布,按模块进行规划。

+

开源贡献

+

已上线

+
    +
  • 如何设置 Databend 开发环境
  • +
  • 轻松了解 Databend 构建
  • +
  • 如何为 Databend 添加新的测试
  • +
  • 如何参与 Databend 开源协作
  • +
  • Databend 全链路追踪
  • +
  • 如何对 Databend 进行基准测试
  • +
  • Databend 性能剖析方法与工具
  • +
  • 如何为 Databend 添加新的函数
  • +
  • Databend 版本发布
  • +
  • Databend 的日常维护是怎么进行的
  • +
  • 常见问题解答
  • +
+

规划中

+
    +
  • 如何实现新的索引
  • +
  • 如何为优化器添加新的规则
  • +
  • 如何设计新的表引擎
  • +
  • 如何添加新的类型
  • +
+

实战演练

+

规划中

+
    +
  • 类型系统
  • +
  • 数据源
  • +
  • 逻辑计划与表达式
  • +
  • 物理计划与表达式
  • +
  • Planner
  • +
  • 查询优化
  • +
  • 查询执行
  • +
  • 支持 SQL
  • +
  • 并行化
  • +
  • 分布式
  • +
  • 测试
  • +
  • 基准测试
  • +
+

专题研讨

+

待定

+

预期会以视频结合文字稿的形式发布。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ ← 推荐阅读的论文清单 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/getting-started/index.html b/docs/getting-started/index.html new file mode 100644 index 0000000..b1eadb1 --- /dev/null +++ b/docs/getting-started/index.html @@ -0,0 +1,245 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +入门指南 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+

入门指南

+
+
+ + + +
+ +
+ + +
+
+
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/getting-started/introduction/index.html b/docs/getting-started/introduction/index.html new file mode 100644 index 0000000..c4e093e --- /dev/null +++ b/docs/getting-started/introduction/index.html @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +简介 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

简介

+ +

Databend 是一个使用 Rust 研发、开源、完全面向云架构的新式数仓。

+

「Databend 内幕大揭秘」将会透过 Databend 的设计与实现,为你揭开面向云架构的现代数据库的面纱。

+

受众

+
    +
  • 高校计算机专业的学生。
  • +
  • 想从事数据库研发的开发者。
  • +
  • 对数据库整体运作原理感兴趣的人员。
  • +
+

范畴

+

「Databend 内幕大揭秘」并不致力于展示数据库领域的全貌,而是紧紧围绕 Databend 来展示现代云数仓的方方面面:设计与架构、算法与实现等。

+

结构

+

本书大概可以分为三个部分:

+
    +
  • 第一部分主要是一些理论材料,包括:介绍数据库的一些重要且基本的概念;以 Databend 为例,展示现代数据库所需具备的一些新特性。
  • +
  • 第二部分将会进入到设计与实现环节:通过 Databend 源码解读,可以了解现代云数仓的实现细节;而如何参与到 Databend 开源贡献的部分将会触及到数据库研发相关的一些内容。
  • +
  • 第三部分则会包含使用 Rust 构建数据库的项目练习,以及对数据库领域论文/技术的研讨,进一步探索数据库之美。
  • +
+

协作

+

「Databend 内幕大揭秘」的内容完全开源,有意向参与协作和贡献的,请参考「贡献相关」一节的内容。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 存储 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..54f9b35 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 内幕大揭秘 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+

Databend 内幕大揭秘

+
+
+ + + + + + + +
+ +
+ + + + + + +
+ +
+ +
+ +
+ + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/minibend/datasource/index.html b/docs/minibend/datasource/index.html new file mode 100644 index 0000000..03ab6a8 --- /dev/null +++ b/docs/minibend/datasource/index.html @@ -0,0 +1,676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +第二弹 - Data Source | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

第二弹 - Data Source

+ +

本篇是 minibend 系列的第二期,将会介绍 Data Source 部分的设计与实现,当然,由于是刚开始涉及到编程的部分,也会提到包括 类型系统错误处理 之类的一些额外内容。

+
+

前排指路视频和 PPT 地址

+

视频(哔哩哔哩):https://www.bilibili.com/video/BV1A84y1Y7Ff/

+

PPT:https://databend-internals.psiace.me/minibend/ppt/minibend-002-datasource.pdf

+
+

类型系统和 Arrow

+

这里仅仅是进行一个初步的介绍,类型系统相关的实现请期待下一期内容。

+

类型系统

+

在构建查询引擎的时候,很重要的一个问题就是「数据在查询引擎中是如何表示的?」。这往往意味着我们需要考虑引入一套类型系统来完成这一工作。

+

为了能够让查询引擎处理来自不同数据源的数据,通常情况下,会选择设计并构建一套能够涵盖所有数据源所涉及的全部数据类型的类型系统,并引入一些额外的机制使得数据能够从数据源轻松转换到这套类型系统之上。

+

当然,如果查询引擎仅仅针对单一数据源设计,或许可以考虑复用数据源的类型系统。

+

行存或者列存

+

解决了数据类型的问题,那么就该考虑数据存储时候的模型。行式存储和列式存储都属于流行的方案,当然,这往往取决于要面对什么样的查询任务。另外还有混合行式和列式存储的方案,但这并不在今天讨论的范畴之中。

+

Column Based

+

左图可以看作列式存储的一个样本,同一列的数据在内存中形成一个连续的结构。
而行式存储则如右图所示,同一行的数据在内存中是连续的。

+

对于 OLAP 系统,往往处理大量数据,更需要关注数据的吞吐量和执行效率,采用列式存储具有天然的优势。

+
    +
  • 只需要读取需要的列,无需经 IO 读取其余列,从而避免不必要的 IO 开销。
  • +
  • 同一列的数据中往往存在大量的重复项,压缩率会非常高,进一步节约 IO 资源。
  • +
  • 利用向量化处理和 SIMD 指令进行优化,提高性能。
  • +
+

Arrow

+

Apache Arrow 是一套通用、跨语言、高性能的列式数据内存格式规范,能够充分利用现代硬件的向量化执行能力。

+

通过引入 Apache Arrow 作为标准的数据交换格式,可以有效提高各种大数据分析系统和应用程序之间的互操作性:

+
    +
  • 高级语言在调用低级语言时,可以通过指针来传递数据,从而免于复制数据。
  • +
  • 数据可以在进程之间有效地传输,减少序列化开销。
  • +
  • 可以在各种开源和商业项目之间建立连接,使得大数据生态之间能够更好集成。
  • +
+

Apache Arrow 现在有多种不同语言的实现,包括 C++、Java、Rust 等。

+

值得关注的基于 Apache Arrow 的 Rust 实现的项目包括 pola-rs/polarsapache/arrow-datafusion,当然,还有 Databend

+

从 Databend 到 minibend

+

Databend 是面向海量数据设计的云数仓,面向分析型工作负载进行设计,采用列式存储,使用 Apache Arrow 作为内存格式规范,并在此基础上设计开发类型系统。minibend 在这一点上将会与 Databend 保持一致。

+
有趣的事实
+

Databend 当前实现使用的是 arrow2 而非 arrow-rs ,主要有以下几个原因:

+
    +
  • arrow2 代码质量更高,设计上弥补了很多 arrow-rs 的缺点。
  • +
  • Databend 早期也是基于 arrow-rs ,但是 arrow-rs 推动 issue、pr 流程较慢,不适合 Databend 的快速迭代节奏。而 arrow2 的作者往往能够提供更加及时和友好的建议和修复,可以和 Databend 一同快速迭代。
  • +
+

Databend 从 arrow2 0.1 和 parquet2 0.1 发布 开始 考虑向 arrow2 迁移 ,十天左右完成 [commons] arrow -> arrow2 并在部分查询上获得性能提高。尽管从当时而言,切换到还处于早期的 arrow2 有些激进,但长远来看是利大于弊的。

+

Data Source

+

Data Source(数据源)是数据处理系统的重要部分,但通常只能依赖经验来谨慎处理。

+

数据源

+

顾名思义,数据源就是数据的来源,倘若没有数据源,数据处理系统就像无根浮萍,自然也谈不上用武之地。

+

数据源可以以不同的形态出现,比如各种各样格式的文件:CSV、JSON、Parquet 等;当然也可以是数据库,之前有很多朋友问过比如 Databend 能不能查询 MySQL 里面的数据(将 MySQL 作为数据源);也可以是内存对象,一个不那么典型的例子是 Databend 里面实现了用于测试向量化性能的 number 表。

+

与不同数据源交互的处理逻辑也有所不同,为了能够更好接入不同数据源,查询引擎需要定义一套统一的接口,并确保能够返回符合预期的数据。对于查询引擎而言,主要关心两类数据:一类是 schema ,用来描述数据的结构,这可以帮助查询引擎对查询计划和表达式进行验证,但并不是所有数据都具有统一的/有效的结构,比如 JSON ;另一类就是具体的数据了,但考虑到查询引擎只需要处理关心的特定数据,所以应该有能力对数据进行过滤,只提取需要的列。

+

Parquet

+

ApacheParquet 是一种开源的、面向列的数据文件格式,用于高效的数据存储和检索。它提供了高效的数据压缩和编码方案,增强了处理大量复杂数据的性能。Parquet 支持多种语言,包括 Java、 C + + 、 Python 等等。.

+

Parquet 受到 Google Dremel 格式启发,作为大数据领域的存储格式,被 iceberg 、hive 等各种系统使用。

+

parquet

+

上图展示了 Parquet 文件的结构,Parquet 的存储模型主要由行组(Row Group)、列块(Column Chuck)、页(Page)组成。

+
    +
  • 行组,Row Group:Parquet 在水平方向上将数据划分为行组,默认行组大小与 HDFS Block 块大小对齐,Parquet 保证一个行组会被一个 Mapper 处理。
  • +
  • 列块,Column Chunk:行组中每一列保存在一个列块中,一个列块具有相同的数据类型,不同的列块可以使用不同的压缩。
  • +
  • 页,Page:Parquet 是页存储方式,每一个列块包含多个页,一个页是最小的编码的单位,同一列块的不同页可以使用不同的编码方式。
  • +
+

从 Databend 到 minibend

+

Databend 的底层存储格式为 Parquet ,过去其他格式的数据需要通过 Streaming Load 或者 Copy Into 等方式转换到 Databend 支持的 Parquet 格式。而在近期的设计和实现中,Databend 开始逐步实现对位于本地/远端的文件进行查询的支持。

+

minibend 将会考虑优先从查询本地现有数据文件开始进行支持。首先是支持 Parquet 作为数据源,但为了方便浏览数据和审计查询结果,或许对 CSV 格式的支持应该提上日程。

+
有趣的事实
+

Databend 中同样包含读取 Parquet 文件作为数据源的代码。关于读取 Parquet 文件作为表的第一版实现可以参考 new table function read_parquet to read parquet files as a table

+

在这个基础上,受 clickhouse-local 启发,@eastfisher 为 Databend 实现 databend-local,支持在不启动 Databend 集群的情况下查询本地文件。

+

代码时间

+

请查阅视频和 PPT 中的对应部分,或者查看 PR #40 | minibend: impl parquet data source 中的代码。

+

前进四:回顾与展望

+

回顾

+

在今天的内容中,我们简单介绍了类型系统和数据源的一些相关内容:

+
    +
  • 类型系统用于处理数据在查询引擎中的表示。
  • +
  • 对于 OLAP 系统而言,基于列式存储会更能发挥现代硬件的能力。
  • +
  • 数据源可以是多种多样的,文件、数据库、内存对象都可以作为数据源。
  • +
  • Apache Arrow 和 Apache Parquet,前者是一套通用、跨语言、高性能的列式数据内存格式规范,后者是一种旨在实现最大空间效率的存储格式。
  • +
+

当然,在这一期的代码时间,我们初步建立了 minibend 的基础,并支持使用 Parquet 文件作为数据源。

+

展望

+

下一期,我们将会进入到类型系统相关的部分,并进一步扩展到逻辑计划和表达式。

+

阅读材料

+

这一次推荐两个博客给大家:

+

一个是 风空之岛@mwish 的技术博客,有关于 Parquet 的一个更详细的系列介绍,并且还有论文阅读的部分。

+

另一个是 数据库内核月报 ,来自阿里云 PolarDB 数据库内核团队。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← Databend 源码阅读: Storage 概要和 Read Partitions +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 如何设置 Databend 开发环境 → +
+
+
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/minibend/index.html b/docs/minibend/index.html new file mode 100644 index 0000000..5b27cd4 --- /dev/null +++ b/docs/minibend/index.html @@ -0,0 +1,248 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +minibend | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/minibend/intro/index.html b/docs/minibend/intro/index.html new file mode 100644 index 0000000..aa81ccc --- /dev/null +++ b/docs/minibend/intro/index.html @@ -0,0 +1,896 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +第一弹 - minibend 简介 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

第一弹 - minibend 简介

+ +

minibend ,一个从零开始、使用 Rust 构建的查询引擎。这里是 minibend 系列技术主题分享的第一期,来自 @PsiACE

+

minibend

+
+

前排指路视频和 PPT 地址

+

视频(哔哩哔哩):https://www.bilibili.com/video/BV1Ne4y1x7Cn

+

PPT:https://databend-internals.psiace.me/minibend/ppt/minibend-001-basic-intro.pdf

+
+

minibend: what, why, how

+

minibend 是什么

+

minibend 是一款从零开始、使用 Rust 构建的查询引擎。

+

查询引擎是数据库系统的一个重要组件,需要具备以下几点能力:

+
    +
  • 访问数据
  • +
  • 提供查询接口
  • +
  • 返回查询结果
  • +
+

通常我们会使用 SQL 也就是结构化查询语言进行交互。

+

minibend 同时也是 Databend Internals,或者说 Databend 内幕大揭秘 这个手册的实战部分。Databend 内幕大揭秘 将会透过 Databend 的设计与实现,为你揭开面向云架构的现代数据库的面纱。

+

为什么要设计 minibend

+

databend

+

特别是在团队已经孵化出 Databend 这个现代开源云数仓的前提下,为什么还需要这样一个项目?

+

先回到 Databend 内幕大揭秘 的初衷,设立这个项目是为了吸引更多人参与到 Databend 的学习、开发和生态建设中,所以目标受众定位在:

+
    +
  • 高校计算机专业的学生。
  • +
  • 想从事数据库研发的开发者。
  • +
  • 对数据库整体运作原理感兴趣的朋友。
  • +
+

但是,Databend 的更新迭代速度、代码量都意味着对刚开始接触 Rust 并尝试参与研发的新朋友会面临一个比较高的门槛。

+

从现存的教程上看,或多或少存在一些问题:

+
    +
  • 不是 Rust 实现(切换语言和生态需要一些努力)
  • +
  • 缺乏 step by step 的体验(细节上需要更深入和艰苦的挖掘)
  • +
  • 从设计思想到实现逻辑上可能与 Databend 有比较明显的差异(存算分离、面向云,这些可能都会有一些抉择)。
  • +
+

所以开启一个新的项目作为连接新开发者和 Databend 之间的纽带就成为一种自然的选择。

+
+

P.S. minibend 致力于解决这些问题,但可能很难完全解决,但至少,先开始运作起来。

+
+

minibend 这个项目计划怎样进行

+

首先,minibend 会提供视频、文章和代码三种材料。文章和代码将会同步到 Databend 内幕大揭秘 的 Repo 中,而视频则会发布到 Databend 的 B 站官方帐号下。欢迎大家持续关注。

+
+

Databend 内幕大揭秘:https://databend-internals.psiace.me/

+

Databend(哔哩哔哩):https://space.bilibili.com/275673537

+
+

更新频率大概是每个月一到两期。内容上会包含必要的相关知识导读、设计和实现相关的说明、并进行回顾和展望。当然,也会不定期精选一部分论文摘要供大家进一步研讨和学习。

+

数据库基础概念

+

在这个部分,我们不会深入数据库的细节,只是从部分组件的视角上进行观察。

+

存储

+

存储解决的是两个问题,存在哪 以及 怎么存

+

对于“怎么存”,不同背景的朋友可能会考虑到一些不同的细节,但大多数时候,可以想象到一个基本的模式是:数据以特定格式写入到某几类文件中,比如 Parquet 甚至 CSV 。

+

但是“存在哪”呢?

+

storage Level

+

过去的一些存储方案更加关注上图所示的存储体系结构,将需要在线处理的数据存放在闪存和硬盘中,用于备份的数据放入光盘和磁带。

+

云存储的兴起和网络带宽的不断提高带来了一些新的变化:云存储能够支持远程保存数据和文件,并通过网络连接进行访问。不仅可以节约拓展物理器件所带来的人力物力消耗,并能够提供更好的弹性以便于即时增减容量,还支持按需按量付费从而做到更好的成本管理与控制。

+

Databend 早期的实现是包含一套分布式文件系统的,但到现在,存储的重心完全转移到云厂商提供(AWS S3, Azure Blob 等)或者自托管(MinIO 等)的云存储之上。

+

尽管云存储越来越重要,但原有的经验和见解依然有效,我们仍然可以使用缓存和并行技术来改善性能,利用冗余来提高可靠性。

+

索引

+

引入索引的好处在于加快数据查询的速度,而缺点则在于构建和维护索引同样需要付出代价。

+

b tree disk

+

不同的索引可以针对不同的场景提供优化,B Tree 能够加速范围查询,而等值查询就可以使用 Hash 索引,BitMap(或者说更常用的 Bloom 索引)可以方便判断数据是否存在。

+

Databend 的索引无需人为创建,由部署的实例自行维护。同时也采用了像 Xor 索引 这样的新技术来进一步加速查询并提高空间利用率。

+

查询执行

+

尽管有各种各样的查询引擎,但具体到查询执行的环节大同小异,这里以 Databend 为例,简单讲一下过程。

+

Query Steps

+
    +
  1. 解析 SQL 语法,形成 AST(抽象语法树)。
  2. +
  3. 通过 Binder 对其进行语义分析,并且生成一个初始的 Logical Plan(逻辑计划)。
  4. +
  5. 得到初始的 Logical Plan 后,优化器会对其进行改写和优化,最终生成一个可执行的 Physical Plan 。
  6. +
  7. 通过 Optimizer 生成 Physical Plan 后,将其翻译成可执行的 Pipeline 。
  8. +
  9. Pipeline 则会交由 Processor 执行框架进行计算。
  10. +
+

那么近年来新兴数据库大多受到 Morsel-Driven Parallelism 这篇论文的启发,在运行时确定任务的并行度,按流水线的方式执行操作,并通过调度策略来尽量保证数据的本地化,在实现 load banlance 的同时最小化跨域数据访问。

+

同时,引入列式存储和向量化执行的技术,可以避免不必要的缓存和 I/O 资源浪费,同时,节约处理数据时需要传递的数据量,为进一步优化提供更多空间。

+

查询优化

+

查询执行的路径并非一成不变,不同的执行计划在不同场景下性能也存在差异,如何为查询选择合适的计划就是查询优化需要关注的内容。

+
    +
  • 更快的速度(更低的延迟)。
  • +
  • 在 OLTP 场景下,则更强调性价比。
  • +
  • 而对于 OLAP 场景,则追求更的高吞吐量。
  • +
+

下面的图片展现的是一种典型的查询优化,对 JOIN 进行重排。

+

JOIN Reorder

+

目前有两种主要的查询优化方案,一种是基于关系代数和算法的等价优化方案,一种是基于评估成本的优化方案。根据命名,不难看出优化的灵感来源和这两种方案在优化上的取舍。

+

那么如何进行查询优化呢?查询优化通常包含以下四个步骤:

+
    +
  • 构建框架来列举可能的计划
  • +
  • 编写转换规则
  • +
  • 引入成本模型来评估不同的计划
  • +
  • 选择最理想的计划
  • +
+

Databend 引入了基于规则的 Cascades 优化器,通过自顶向下探索、模式匹配以及记忆化来提供更好的查询优化能力。

+

大规模并行处理

+

大规模并行处理是大数据计算引擎的一个重要特性,可以提供高吞吐、低时延的计算能力。那么,当我们在讨论大规模并行处理时,究竟在讨论什么?

+

mpp

+

大规模并行处理(MPP,Massively Parallel Processing)意味着可以由多个计算节点(处理器)协同处理程序的不同部分,而每个计算节点都可能具备独立的系统资源(磁盘、内存、操作系统)。

+

计算节点将工作拆分成易于管理、调度和执行的任务执行,通过添加额外的计算节点可以完成水平拓展。随着计算节点数目的增加,对数据的查询处理速度就越快,从而减少大数据集上处理复杂查询所需的时间。

+

在近些年,MPP 和分布式设计往往会同时出现在同一套系统中。

+

分布式

+

分布式

+

从某种视角上看,分布式系统与 MPP 系统有着惊人的相似。比如:通过网络连接、对外作为整体提供服务、计算节点拥有资源等。但是这两种架构仍然会有一些不同。

+
    +
  • 从设计目标上看,分布式系统致力于改善系统的可靠性和可用性,而 MPP 系统需要充分利用计算节点的并行能力从而提高整体性能。
  • +
  • 具体到实现上,分布式系统降低了对网络的需求,采用局域网或广域网相连,拓展性进一步增强。而 MPP 系统为了充分利用计算节点的处理能力,依赖高速网络进行通讯。
  • +
  • 同时,由于节点地位不同,分布式系统除了协同执行任务之外,还具有自治执行任务的能力;而 MPP 系统则专注于任务的协同执行。
  • +
+

Rust 不完全指南

+

刚刚介绍了数据库相关的一些基本概念,现在让我们将目光转向 Rust ,来一同了解这个正在走向流行的编程语言。

+

The Rust Programming Language

+

rust

+

Rust 官方宣传语是:Rust 是一门赋予每个人构建可靠且高效软件能力的语言,现在距离它第一个版本发布也已经过去10年。

+

Rust 没有运行时和垃圾回收,速度快且内存利用率高,几乎可以与 C 和 C++ 竞争。

+

Rust 的类型系统和所有权模型为内存安全和线程安全提供保障,在编译期就能够消除各种各样的错误。

+

特别值得一提的是,Rust 工具链内置很多实用工具,可以切实改善生产力:包管理器、构建工具、格式化程序、用于代码审计的 Clippy 等等。

+

函数

+
#[allow(dead_code)]
+// Functions
+// `i32` is the type for 32-bit signed integers
+fn add2(x: i32, y: i32) -> i32 {
+    // Implicit return (no semicolon)
+    x + y
+}
+
+

上面函数是两个 32 位整数相加,返回值也是一个 32 位整数。值得注意的是,我们需要标注返回值类型,而函数体中的 x + y 是一种隐式返回,所以不需要添加 return 关键字,当然,也不需要在末尾添加分号。只添加末尾分号的话,则会将其视为普通语句执行,就没有返回值了(报错)。

+
// This is the main function
+fn main() {
+    // Statements here are executed when the compiled binary is called
+
+    // Print text to the console
+    println!("Hello World!");
+}
+
+

经典的 Hello World 程序,大家应该会感觉到熟悉。main 函数也是 Rust 程序的入口点。通过调用 println! 这个宏,可以输出文本到终端。

+

类型

+
// Struct
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+// A struct with unnamed fields, called a ‘tuple struct’
+struct Point2(i32, i32);
+
+// Enum with fields
+enum OptionalI32 {
+    AnI32(i32),
+    Nothing,
+}
+
+// Generics //
+struct Foo<T> { bar: T }
+
+// Traits (known as interfaces or typeclasses in other languages) //
+trait Frobnicate<T> {
+    fn frobnicate(self) -> Option<T>;
+}
+
+impl<T> Frobnicate<T> for Foo<T> {
+    fn frobnicate(self) -> Option<T> {
+        Some(self.bar)
+    }
+}
+
+

除了基本的字符串、整数、浮点数、布尔类型之外,Rust 还支持结构体和枚举类型,代码片段提供了一个基本的例子。为这些类型可以实现特定的方法,以支持各种各样的操作,通用的接口可以使用 trait 关键字进行定义。

+

模式匹配

+
let foo = OptionalI32::AnI32(1);
+match foo {
+    OptionalI32::AnI32(n) => println!("it’s an i32: {}", n),
+    OptionalI32::Nothing  => println!("it’s nothing!"),
+}
+
+// Advanced pattern matching
+struct FooBar { x: i32, y: OptionalI32 }
+let bar = FooBar { x: 15, y: OptionalI32::AnI32(32) };
+
+match bar {
+    FooBar { x: 0, y: OptionalI32::AnI32(0) } =>
+        println!("The numbers are zero!"),
+    FooBar { x: n, y: OptionalI32::AnI32(m) } if n == m =>
+        println!("The numbers are the same"),
+    FooBar { x: n, y: OptionalI32::AnI32(m) } =>
+        println!("Different numbers: {} {}", n, m),
+    FooBar { x: _, y: OptionalI32::Nothing } =>
+        println!("The second number is Nothing!"),
+}
+
+

模式是 Rust 中特殊的语法,它用来匹配类型中的结构,看起来有点像 switch,但要更加强大和简洁。无论类型是简单还是复杂,结合使用模式和 match 表达式以及其他结构可以提供更多对程序控制流的支配权。通过将一些值与模式相比较来使用它。如果模式匹配这些值,就可以对值的部分进行相应处理。

+

控制流

+
// for and ranges
+for i in 0u32..10 {
+    print!("{} ", i);
+}
+println!("");
+// prints `0 1 2 3 4 5 6 7 8 9 `
+
+// `if` as expression
+let value = if true {
+    "good"
+} else {
+    "bad"
+};
+
+// `while` loop
+while 1 == 1 {
+    println!("The universe is operating normally.");
+    // break statement gets out of the while loop.
+    //  It avoids useless iterations.
+    break
+}
+
+// Infinite loop
+loop {
+    println!("Hello!");
+    // break statement gets out of the loop
+    break
+}
+
+

上面是一些常见的控制流语法,for 循环和范围迭代看起来和其他语言很相似;而通过 let - if 语句,可以轻松将 if 当作表达式来使用;当然,Rust 同样支持 while 循环和无限 loop 循环。

+

内存安全与指针

+
// Owned pointer – only one thing can ‘own’ this pointer at a time
+// This means that when the `Box` leaves its scope, it can be automatically deallocated safely.
+let mut mine: Box<i32> = Box::new(3);
+*mine = 5; // dereference
+// Here, `now_its_mine` takes ownership of `mine`. In other words, `mine` is moved.
+let mut now_its_mine = mine;
+*now_its_mine += 2;
+
+println!("{}", now_its_mine); // 7
+// println!("{}", mine); // this would not compile because `now_its_mine` now owns the pointer
+
+

Owned Pointer,一次只能有一个对象“拥有”此指针,这意味着当 Box 离开其作用域时,它可以安全地自动释放。

+
// Reference – an immutable pointer that refers to other data
+// When a reference is taken to a value, we say that the value has been ‘borrowed’.
+// While a value is borrowed immutably, it cannot be mutated or moved.
+// A borrow is active until the last use of the borrowing variable.
+let mut var = 4;
+var = 3;
+let ref_var: &i32 = &var;
+
+println!("{}", var); // Unlike `mine`, `var` can still be used
+println!("{}", *ref_var);
+// var = 5; // this would not compile because `var` is borrowed
+// *ref_var = 6; // this would not either, because `ref_var` is an immutable reference
+ref_var; // no-op, but counts as a use and keeps the borrow active
+var = 2; // ref_var is no longer used after the line above, so the borrow has ended
+
+

Reference – 引用其他数据的不可变指针。当引用某个值时,我们称该值已被 “借用” 。当一个值被不可变借用时,它不能被修改或移动。借用直到在最后一次使用借用变量之前会一直处于活跃状态。

+
// Mutable reference
+// While a value is mutably borrowed, it cannot be accessed at all.
+let mut var2 = 4;
+let ref_var2: &mut i32 = &mut var2;
+*ref_var2 += 2;         // '*' is used to point to the mutably borrowed var2
+
+println!("{}", *ref_var2); // 6 , // var2 would not compile.
+// ref_var2 is of type &mut i32, so stores a reference to an i32, not the value.
+// var2 = 2; // this would not compile because `var2` is borrowed.
+ref_var2; // no-op, but counts as a use and keeps the borrow active until here
+
+

可变引用,如果你有一个对该变量的可变引用,你就不能再创建对该变量的引用。

+

上面的这些 Rust 片段节选自 Learn X in Y minutes ,只进行了一些粗浅的介绍。

+

如果想要进一步学习,建议查阅以下资料:

+ +

前进四:回顾与展望

+

回顾

+

首先我们介绍了 minibend 这个系列课程,一方面,这会是一个从零开始、使用 Rust 构建的查询引擎;另一方面,它会参考 Databend 的设计,并致力于降低数据库内核开发的门槛。

+

而在数据库相关基础知识的部分,云存储为现代数据库设计带来了一些新变化,而不同的索引又可以为不同的查询场景带来性能优化,接着是查询执行和查询优化的相关知识,以及对大规模并行处理和分布式技术的介绍。

+

Rust 不完全指南里,从函数、类型、模式匹配、控制流、内存安全与指针进行了一个简单的介绍,为阅读 Rust 代码提供了一个简单的基础。

+

展望

+

下一期,我们将会介绍 Apache Arrow - 一种列式存储的内存格式规范,以及查询引擎中的类型系统,然后试着写一些关于数据源的代码。

+

阅读材料

+

books

+

本期课程推荐两本书给大家:

+

一本是 The Rust Programming Language ,这是 rust 官方出品的 Rust 书籍,一般被称作 the book 。

+

另一本是 How Query Engines Work ,Andy 同时也是 Datafusion 和 Ballista 的作者,不过这本书使用的是 kotlin 。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← Databend 源码阅读: Storage 概要和 Read Partitions +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 如何设置 Databend 开发环境 → +
+
+
+ + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/productivity-topics/databend-rustchinaconf/index.html b/docs/productivity-topics/databend-rustchinaconf/index.html new file mode 100644 index 0000000..da3906b --- /dev/null +++ b/docs/productivity-topics/databend-rustchinaconf/index.html @@ -0,0 +1,845 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +使用 Rust 构建云原生数仓 Databend | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

使用 Rust 构建云原生数仓 Databend

+

本篇文章是对 Databend 在 RustChinaConf 2022 上演讲的一个全文回顾。涉及「Databend 的架构和设计」以及「Databend 团队的 Rust 之旅」。

+

Databend 简介

+

那么,在进入正题之前,让我们先来回答这样一个问题:“什么是 Databend”?

+

什么是 Databend

+

what's databend

+

官方的说法是:Databend 是一个使用 Rust 研发、开源、完全面向云架构的新式数仓。

+

借用当下大数据分析领域最流行的两个数据库产品打个比方:Databend 就像是开源的 Snowflake 或者说云原生的 Clickhouse 。

+ +

Databend 特性

+

作为新式数仓,Databend 有哪些基本特性呢?

+

databend features

+
    +
  • 首先是弹性:得益于存算分离的架构与设计,Databend 完全可以做到按需、按量弹性扩展。
  • +
  • 而向量化执行引擎、单指令流多数据流(SIMD)、大规模并行处理(MPP),共同为 Databend 的性能保驾护航。
  • +
  • Databend 的存储引擎受 Git 启发,使用快照存储数据。支持 TIME TRIVAL,可以轻松回滚到任意时间节点。
  • +
  • 另外,近年来经常提的一个词叫“海量数据”,在这些数据中,由各种各样程序产生的半结构化数据是占到相当大一部分比重的。Databend 内置 ARRAY, MAP, JSON 这些数据类型,能够帮助用户进一步发掘半结构化数据的价值。
  • +
  • 作为一个新生数仓,不管从研发还是客户的角度上,都非常关心生态问题。Databend 使用 SQL 语句进行查询,并且对 MySQL 和 ClickHouse 协议进行兼容,从而与现有工具和 BI 系统无缝集成,比如数据科学家喜欢的 Jupyter、以及用于生成图表的 Metabase 。
  • +
+

Databend 架构与设计

+

刚刚介绍了 Databend 的一些基本信息,接下来,让我们一起走进 Databend 的架构和设计。看看一个存算分离的云原生数仓该是什么样子。

+

总览

+

databend arch

+

右边是 Databend 的一个架构图。

+

最上一层对接 AI、BI 等应用,最下一层打通 S3、GCS、IPFS 等存储系统。中间三层则是 Databend 的主体部分。

+

Databend 可以分成 Meta、Computing 和 Storage 三层,也就是元数据、计算和存储。

+

说是存储层,其实叫做数据访问层更贴切一些。

+

架构与设计 - Meta

+

databend arch - meta

+

Meta 是一个多租户、高可用的分布式 key-value 存储服务,具备事务能力。

+

它会负责管理元数据,像索引和集群的一些信息;Meta 还具备租户管理的能力,包括权限管理以及配额使用统计。

+

当然,安全相关的部分也由 Meta 承担,比如用户登录认证。

+

架构与设计 - Computing

+

databend arch - computing

+

计算层可以由多个集群组成,不同集群可以承担不同的工作负载。每个集群又可以由多个计算节点(node)组成。

+

计算层中的核心组件有三个:

+
    +
  • 执行计划,也就是 Planner :用于指导整个计算流水线的编排与生成。
  • +
  • 优化器,Optimizer:基于规则做一些优化,比如谓词下推或是去掉不必要的列。
  • +
  • 处理器 (Processors)是执行计算逻辑的核心组件:Databend 最近还落地了 Pull & Push 模型的流水线,大幅提高了处理器的执行效率。整个 Pipeline 是一个有向无环图,每个节点是一个处理器,每条边由处理器的 InPort 和 OutPort 相连构成,数据到达不同的处理器进行计算后,通过边流向下一个处理器,多个处理器可以并行计算,在集群模式下还可以跨节点分布式执行。
  • +
+

架构与设计 - Storage

+

databend arch - storage

+

Databend 使用 Parquet 格式储存数据,为了加快查找(Partition Pruning),Databend 为每个 Parquet 提供了自己的索引:min_max,bloom 。这一部分工作是放在存储层完成的。

+

前面其实有提到,存储层的另一个说法是数据访问层。

+

一方面,它支持对接多种对象存储服务,像 AWS S3 和 Azure Blob,做到让数据自由流动。当然也支持在本地文件系统做测试,但是没有做专门的性能优化。

+

另一方面,存储层也支持挂载多种 catalog,在社区小伙伴的帮助下,Databend 完成了 Hive 引擎的对接,支持进行一些简单的查询。

+

What's New

+

“存算分离”、“云原生”对于新式数仓而言,只能算是基本特性。除了卯足劲大搞性能优化之外,还有没有其他值得关注的地方呢?让我们一起来看一下。

+

更加友好的查询体验

+

好的产品是一定会强调用户体验的,Databend 作为一款云数仓产品,自然要关注查询体验。

+

那么,在新的 parser 和 planner 中,Databend 引入了语义检查的环节,在查询编译过程中就可以拦截大部分错误。

+

databend - query check

+

右图展示的正是两类语义错误,一类是使用了不存在的 Column ,一类是 Column 具有歧义。

+

全新 Planner

+

其实新 planner 除了更加友好的查询体验之外,还为支持复杂查询打下了扎实的基础。那么 Databend 现在可以支持多种 Join 和关联子查询,感兴趣的小伙伴可以体验一下。

+

在引入新 Planner 之后,计算层的架构得到进一步的划分,当一个查询请求进来以后,会经过以下处理:

+

databend - planner

+
    +
  • 基于 nom 定制的解析器会负责生成抽象语法树,值得一提的是,在新 parser 中支持新语句也非常方便。
  • +
  • Binder 会对语法树进行语义分析,并且生成一个初始的 Logical Plan(逻辑计划)。
  • +
  • 基于规则的 Cascades 优化器框架会对 Logical Plan 进行改写和优化,最终生成一个可执行的 Physical Plan 。
  • +
  • Physical Plan 经由解释器翻译成可执行的 Pipeline,并交由 Databend 的 Processor 执行框架进行计算。
  • +
  • 在处理器执行之后得到一些 data blocks 用于生成最终的查询结果。
  • +
+

对新 plannere 感兴趣的朋友可以看一下下面列出的内容。

+ +

类型安全的 Expression

+

Databend 最近正在研发一套全新的表达式框架,其中包含一套形式化的类型系统,算是使用 Rust 自定义类型系统的最佳范例。

+

databend - typed type

+

通过引入形式化方法,可以提高设计的可靠性和健壮性。对应到新表达式框架中:

+

一方面,引入类型检查,可以拦截 SQL 编译阶段的所有类型错误,运行时不再需要关注类型信息。

+

另一方面,实现了类型安全的向下转型(downcast),得益于 Rust 的类型系统,只要函数能够正常编译就不需要担心类型转换的问题。

+

当然,性能和开发体验也是新表达式框架非常关心的部分:

+

通过 Enum 进行静态分发,可以减少运行时开销,降低开发难度。

+

另外,在函数签名中大量使用泛型,减少手写的重载。

+

图的右侧给出了一个例子:用几行代码即可定义一个快速、类型安全、自动向下转型并支持向量化的二元函数。

+

如果对使用 Rust 自定义类型系统感兴趣,可以阅读下面列出的材料。我们也正在积极推进这套表达式框架的开发和迁移工作,欢迎体验。

+ +

In Rust Way

+

作为用 Rust 开发的大型项目,Databend 在一年半的迭代中也积累了一些经验,借这个机会和大家分享一下。

+

快速迭代

+

Databend 选择 Rust ,其实有很多原因:极客精神、健壮性等。

+

团队成员老 C 也分享了他的一个想法:

+
    +
  • 在解决可预期的“编译时间”和难预期的“运行时问题”之间,更应该选择前者。人生苦短,浪费时间在解决运行时的各种内存问题太不值当了。
  • +
+

参见:周刊(第7期):一个C系程序员的Rust初体验 - codedump的网络日志

+

databend - fast iter

+

这里给大家分享一下 Databend 的快速迭代方法论。

+
    +
  • 首先是要及时更新工具链和依赖关系,分享上游的最新成果。
  • +
  • 利用好 fmt 和 clippy 这些工具,打造适合团队协作的代码风格。
  • +
  • 作为大型项目,依赖管理其实很重要,要利用好生态中的一些管理和审计工具。 +
      +
    • 比如大家常用的 audit ,可以帮忙审计安全漏洞。
    • +
    +
  • +
  • 另外,也要积极探索一些能够改善开发体验的新工具。 +
      +
    • 比如 nextest,能够在数十秒将几分钟的测试跑完。
    • +
    • 再比如 goldenfiles 测试,在 similar 的加持下,可以很方便找出预期与实际结果的差异。
    • +
    +
  • +
+

测试风格

+

databend - test style

+

Databend 的单元测试组织形式有别于一般的 Rust 项目,像上图左侧展示的这样,针对性地禁用了 src 目录下的 doctest 和 test 。

+

主要的优点就是节省构建测试需要的时间。

+

一方面,减少遍历和检查的环节,并削减要构建的 test 目标;另一方面,如果不修改 src ,添加新单元测试时只需要编译对应的 test 目标。

+

当然这样做也有缺点:不利于软件设计上的分层,需要引入编码规范并且更加依赖开发者的主动维护。

+

上图右侧是 goldenfiles 的一个测试文件片段。Golden Files 测试是一种常用的测试手段,相当于是一类快照测试。我们计划大量使用它来替代手写断言。一方面变更测试文件无需重新编译,另一方面提供自动生成的办法可以减轻写测试的痛苦。

+

测试相关的一些阅读材料见下:

+ +

代码演进

+

重构要兼顾性能和开发人员的心智负担,这里分享 Databend 代码演进的两个例子。

+

eg.1

+

databend - code refactor 1

+

第一个例子是大家编写异步代码时常用的 async trait ,用起来很方便,就像左上角的例子,但是有一些小缺点:

+

一是动态调度会带来开销,比较难做一些编译器的优化。 +二是内存分配也会带来开销,每次调用都需要在堆上新建一个对象。如果是经常调用的函数,就会对程序的性能造成比较大的影响。

+

那么有没有解决办法呢?左下角的例子中使用泛型关联类型对它进行了改写,虽然避免了开销,但是实现起来还是相对复杂一些。

+

右上角是使用 SAP 的同学作的 async-trait 分叉,只需要加一个 unboxed_simple 就可以做到同样的效果,省心省力。

+ +

eg.2

+

databend - code refactor 2

+

第二个例子是关于分发的,分发其实就是要确定调用接口时是调用哪个实例和它具体的类型。分发的方式不同,其成本也不同。

+

左上角的例子是利用 trait object 动态分发,当然这会有一些开销。

+

左下角使用 enum 进行静态分发,从语法上更便利。有数十倍的一个提升,但是 enum-dispatch 实现上比较硬核,基本上无法自动展开,需要自己手写规则。

+

在新表达式框架中,使用 EnumAsInner 完成静态分发,代码更简洁,而且对 IDE 也更友好。

+ +

踩坑小记

+

尽管 Rust 是一门健壮的语言,但程序的健壮性还需要开发者自己用心,这里分享两例完全可以避免的内存问题。

+

eg.1

+

databend - bug 1

+

之前,上图中的代码片段没有加环境变量判断,导致程序会默认开启日志发送服务。

+

但可能这个时候集群里没开对应的日志收集服务,没发的日志被 buffer 住,时间久了越攒越多,自然引发 OOM 。

+

eg.2

+

databend - bug 2

+

左图这个代码片段其实 tracing 的文档中已经给了提示。

+

由于进入的 span 在异步执行结束后无法正确释放,会造成内存。onesignal 为此专门写了一篇文章,比较值得读。

+ +

社区

+

Databend 的成长离不开 Rust 社区和开源共同体,Databend 社区也在为共筑更好的 Rust 生态而努力。

+

开源项目

+

这里介绍三个 Databend 社区维护的开源项目。

+

openraft

+
    +
  • +

    https://github.com/datafuselabs/openraft

    +

    openraft 是基于 tokio 运行时的异步共识算法实现,是披着 Raft 外壳的 Paxos,旨在成为构建下一代分布式系统的基石。

    +

    目前已经应用在 SAP / Azure 的项目中。

    +
  • +
+

opendal

+
    +
  • +

    https://github.com/datafuselabs/opendal

    +

    opendal 的口号是:让所有人都可以无痛、高效地访问不同存储服务。

    +

    近期的提案包括实现一个命令行工具,以操作不同服务中存储的数据,并支持数据迁移。

    +
  • +
+

opensrv

+
    +
  • +

    https://github.com/datafuselabs/opensrv

    +

    opensrv 为数据库项目提供高性能和高可度可靠的服务端协议兼容,建立在 tokio 运行时上的异步实现。目前在 CeresDB 中得到应用。

    +
  • +
+

课程

+

databend - course

+

自 21 年 8 月起,Databend 和 Rust 中文社区、知数堂, +启动了面向 Rust 和数据库开发人员的公开课计划,前后一共输出 34 期课程。

+ +

新一轮的公开课也在积极筹备,敬请期待。

+

上游优先

+

对待开源,Databend 一直秉承着上游优先的理念。也就是说开源协作理所当然地需要将变更反馈给社区。不光是做一个好的用户,也要做一个好的开发者。

+ +

一个典型的例子是 arrow2 ,Databend 的核心依赖,我们应该是最早一批使用 arrow2 的项目。在 arrow2 的贡献者中有 9 位是 Databend Labs 成员,其中有三位是 top 15 贡献者。

+

Databend Cloud

+

Databend 是一款云数仓,不仅仅是云原生数仓,更是云上数仓。

+

一站式数据分析云平台

+

databend - cloud

+

Databend Cloud 是 Databend 打造的一款易用、低成本、高性能的新一代大数据分析平台,让用户更加专注数据价值的挖掘。

+

目前 Databend Cloud 正处于测试阶段,有需要的朋友可以访问 app.databend.com 注册帐号体验。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 分布式 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Databend 源码阅读: 开篇 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/productivity-topics/index.html b/docs/productivity-topics/index.html new file mode 100644 index 0000000..30f3fc3 --- /dev/null +++ b/docs/productivity-topics/index.html @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +特性探索 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+ +
+
+ + + + + + + + + + + + + + diff --git a/docs/productivity-topics/quality-assurance-in-databend/index.html b/docs/productivity-topics/quality-assurance-in-databend/index.html new file mode 100644 index 0000000..e191c62 --- /dev/null +++ b/docs/productivity-topics/quality-assurance-in-databend/index.html @@ -0,0 +1,681 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 的质量保障 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 的质量保障

+

质量保障(Quality Assurance)用于确认产品和服务能够满足规定的质量需求。让我们一起来看一下 Databend 的质量保障实践。

+

作为从 Day 1 就开源的现代云数仓,Databend 依托于 GitHub Actions 建立了一套相对完备的质量保障体系,以支撑快速迭代。

+

简介

+

Databend 的质量保障大概分为以下几个方面:

+

流程篇

+
    +
  • Issue 管理
  • +
  • 周期性维护
  • +
  • 版本发布与路线图
  • +
+

基础篇

+
    +
  • 代码审计
  • +
  • 跨平台编译
  • +
  • 性能监控
  • +
+

测试篇

+
    +
  • 单元测试
  • +
  • 功能测试
  • +
+

流程篇

+

好的流程有助于质量保障,从而持续推动 Databend 的进步。

+

Issue 管理

+

质量保障离不开对 Issue 的管理。Features / Bugs / Questions ,不同的分类决定了投递的形式和处理的方式。

+

目前,Databend 格外关注 Bugs 类 Issue ,要求提供包括版本、报错、复现步骤并要求确认是否会提交相关的补丁。

+

Databend 团队成员会检查 Issue 、沟通确认情况并进行评估。如确认存在需求,则会根据优先级和排期处理;而对于缺陷,则会及时修复并进行验证。

+

周期性维护

+

周期性维护的目标是:在保证 Databend 稳定的前提下,有序推进工具链和依赖的更新。

+

Databend 会在每月初进行一次集中维护:

+
    +
  • 将工具链更新到当前最新版本,应用对应的审计规则;
  • +
  • 更新并协调依赖版本,确保构建工作能够有效进行。
  • +
+

在无特殊需要的情况下,避免工具链和依赖版本的频繁变更,可以降低协作成本,帮助开发工作的稳定推进。

+

版本发布与路线图

+

当前 Databend 采用 nightly release 和 milestone release 结合的发布机制。每日都会发布的 nightly release 有助于日常情况的跟踪,而不定期的 milestone release 则方便进行阶段性的规划。

+

Databend 的路线图由年度规划和版本计划组成,年度规划将会列出当年关注的一些主要目标,而版本计划则会根据当前的开发情况和 issue 组成进行调整。

+

随着版本和路线图的不断演进,对 Databend 的质量也会有更高的要求,比如稳定性、跨版本兼容性等。

+

基础篇

+

除了测试之外,推进质量保障的一些常规手段。

+

代码审计

+

代码审计是保障 Databend 代码质量和稳定构建的第一道关口,在这一过程中,主要关注以下两个方面:

+
    +
  • 代码检查:许可协议是否正确设置、代码有无格式化、以及通过静态分析检测常见的错误和可以优化的代码。
  • +
  • 依赖审计:是否引入了存在安全漏洞的依赖,以及是否引入了多余的依赖。
  • +
+

代码审计不光是提高质量的一种手段,同时也能降低 reviewer 的负担。

+

跨平台编译

+

作为跨平台的 Cloud Warehouse ,确保在各个平台上的顺利编译也是质量保障的重要环节。

+

Databend 的跨平台编译主要是针对 Linux(GNU) 和 MacOS 这两个平台,且完成对 x86aarch64 两种体系结构的支持。额外地,在 release 阶段,也会构建针对 Linux(MUSL) 的静态编译版本。

+

对于日常提交,需要保证能够顺利在这些平台上完成构建。

+

性能监控

+

性能监控是质量保障中必不可少的一环,目前 Databend 主要关注:

+
    +
  • In-Memory 向量化引擎的性能:一些数值计算任务,由 numbers 表函数提供百亿级别的数据。
  • +
  • 常规查询分析任务下的性能:目前选用 OnTime 数据集和对应的一组常见分析查询。
  • +
+

所有数据都会收集到 datafuselabs/databend-perf 这一 repo 中。访问 https://perf.databend.rs 即可查看可视化结果。

+

测试篇

+

测试是确保开发迭代和质量控制的重要内容。

+

单元测试

+

单元测试主要考虑基本组成单元(如:函数、过程)的正确性。

+

Databend 目前共有接近 800 条单元测试,对重点函数做到了应测尽测。通过 Mock 部分全局状态,帮助开发者更加容易的编写测试用例。

+

所有单元测试按 crate 进行组织,分布在对应的 tests/it 目录下,并按对应模块进行组织。这种编排方式可以减轻测试构建压力,在修改/添加新的测试时,无需重复编译对应的 lib 。

+

功能测试

+

每个功能都是由若干函数/过程组成的,功能测试正是为评估功能的正确性而设立。功能测试会以 standalone 和 cluster 两种模式进行,以确保 Databend 的分布式执行功能,。

+

当前 Databend 的功能测试主要由 sqllogictest 测试和 stateful 测试两个部分组成,这两类测试都可以在 tests 目录下找到。

+

sqllogictest 即 SQL 逻辑测试,是为了解决之前的 stateless 的一些旧有问题而专门设计实现的测试方案。RFC | New SQL Logic Test Framework 中介绍了其基本背景和方案概要。

+

Databend stateful 功能测试目前采用 Clickhouse 的方法,将测试所需执行的 SQL 集放入一个文件,预期结果集放入另一个文件。在测试时会调用 SQL 集生成对应的测试结果集,并与预期结果集进行对比。

+

进一步探索

+

上面简要介绍了 Databend 日常开发中涉及的质量保障内容,但质量保障体系仍然处于持续演进的过程中,这里列出了一些值得关注的内容:

+
    +
  • SQLancer,一款针对数据库管理系统DBMS的自动化安全测试工具。Databend 计划使用这一工具探测潜在的逻辑错误,目前作为 OSPP 2022 项目 ,由 @hanyisong 同学负责开发,可以关注 SQLancer supports databend
  • +
  • Domain-aware Fuzzing,Databend 有一个针对 SQL Parser 的简单模糊测试,但只是生成一些随机字符串。一个比较值得探索的方式是像 sqlsmith 那样的方案,生成更有意义和具有针对性的测试用例。
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 分布式 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Databend 源码阅读: 开篇 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/productivity-topics/the-databend-way-to-sky/index.html b/docs/productivity-topics/the-databend-way-to-sky/index.html new file mode 100644 index 0000000..5b25f0c --- /dev/null +++ b/docs/productivity-topics/the-databend-way-to-sky/index.html @@ -0,0 +1,712 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +多云转晴:Databend 的天空计算之路 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

多云转晴:Databend 的天空计算之路

+

本文介绍了天空计算的概念和背景,以及 Databend 的跨云数据存储和访问。欢迎部署 Databend 或者访问 Databend Cloud ,即刻探索天空计算的无尽魅力。

+

+

背景

+

云计算时代的开端可以追溯到 2006 年,当时 AWS 开始提供 S3 和 EC2 服务。2013 年,云原生概念刚刚被提出,甚至还没有一个完整的愿景。时间来到 2015 年 CNCF 成立,接下来的五年中,这一概念变得越来越流行,并且成为技术人绕不开的话题。

+
+

根据 CNCF 对云原生的定义:云原生技术使组织能够在公共、私有和混合云这类现代、动态的环境中构建和运行可扩展的应用程序。典型示例包括:容器、服务网格、微服务、不变基础设施和声明式 API 。

+
+

+

然而,无论是公有云还是私有云、无论是云计算还是云服务,在天空中都已经存在太多不同类型的“云”。每个“云”都拥有自己独特的 API 和生态系统,并且彼此之间缺乏互操作性,能够兼容的地方也是寥寥无几。云已经成为事实上的孤岛。这个孤岛不仅仅是指公有云和私有云之间的隔阂,还包括了不同公有云之间、不同私有云之间、以及公有云和私有云之间的隔阂。这种孤岛现象不仅给用户带来了很多麻烦,也限制了云计算的发展。

+

2021 年 RISELab 发表了题为 The Sky Above The Clouds 的论文,讨论关于天空计算的未来。天空计算将云原生的思想进一步扩展,从而囊括公有云、私有云和边缘设备。其目标是实现一种统一的 API 和生态体系,使得不同云之间可以无缝地协作和交互。这样一来,用户就可以在不同的云之间自由地迁移应用程序和数据,而不必担心兼容性和迁移成本的问题。同时,天空计算还可以提供更高效、更安全、更可靠的计算服务,从而满足用户对于云计算的不断增长的需求。总体上讲,天空计算致力于允许应用跨多个云厂商运行,实现多云之间的互操作性。

+

+

(上图引自论文,展示不同类型的多云与天空的区别)

+

The Databend Way

+

跨云的关键

+

Databend 能够满足用户在不同的云之间自由地访问数据并进行查询,而不必担心兼容性和迁移成本的问题。同时,Databend 还可以提供更高效、更安全、更可靠的计算服务,从而满足用户对于云计算的不断增长的需求。从这个角度来看,Databend 已经初步形成了一套天空计算的解决方案。那么,对 Databend 而言,跨云的关键到底落在哪里呢?

+

+

(上图所示为 Databend Cloud 架构示意图)

+

Databend 采用存算分离的架构,并完全面向云对象存储进行设计决策。得益于存储与计算分离、存储与状态分离,Databend 可以实现对资源的精细化控制,轻松部署与扩展 Query 和 Meta 节点 ,并支持多种不同的计算场景和存储场景,而无需考虑跨云数据管理与移动的问题。

+

Query 节点和 Meta 节点本身都是轻量化的服务,并且对于部署环境没有严格的依赖。但数据的存储和访问管理就不一样,我们需要考虑不同云服务之间的 API 兼容性、以及如何与云服务本身的安全机制交互从而提供更安全的访问控制机制。对于 Databend 而言,跨云,或者说实现天空计算的关键,就落在数据的管理与访问之上。

+

+

(OpenDAL 可以将数据访问问题从 M*N 转化为 M+N)

+

为了解决这一问题,Databend 抽象出一套统一的数据访问层(OpenDAL,现在是 Apache 软件基金会旗下的孵化项目),从而屏蔽了不同云服务之间的 API 兼容性问题。在接下来的部分,我们将会从不同的视角来观察 Databend 的无痛数据访问体验,体验真正完全云原生的天空计算的魅力。

+

数据存储

+

Databend 存储后端的细节隐藏在简单的配置之下,通过修改配置文件就可以轻松地在十数种存储服务之间切换。例如,如果你想使用 AWS S3,只需要指定类型为 s3 即可,Databend 会自动尝试使用 IAM 来进行认证。如果你想使用其他与 S3 兼容的对象存储服务,也可以通过 endpoint_url 等设置来调整。

+
[storage]
+type = "s3"
+
+[storage.s3]
+bucket = "databend"
+
+

当然,仅支持 S3 兼容的对象存储服务还不够。Databend 通过 OpenDAL 实现了 Google Cloud Storage、Azure Blob、Aliyun OSS、Huawei OBS 和 HDFS 等服务的原生存储后端支持。 +这意味着 Databend 可以充分利用各种供应商提供的 API,为用户带来更优秀的体验。例如,Aliyun OSS 的原生支持使得 Databend 可以通过 Aliyun RAM 对用户进行认证和授权,无需设置静态密钥,从而大大提高安全性并降低运维负担。

+

+

(上图选自阿里云官网,访问控制场景与能力)

+

此外,原生支持还可以避免出现非预期行为,并与服务供应商提供更紧密的集成。虽然各大厂商都提供了 S3 兼容 API,但它们之间存在微妙差异,在出现非预期行为时可能会导致服务性能下降或读写数据功能异常。Google Cloud Storage 提供了 S3 兼容的 XML API,但却没有支持批量删除对象的功能。这导致用户在调用该接口时遇到意外错误。而 Google Cloud Storage 的原生支持使 Databend 不必担心 GCS 对 S3 的兼容实现问题对用户业务造成影响。

+

总之,Databend 通过为各个服务实现原生支持来为用户提供高效可靠的数据分析服务。

+

数据管理

+

前面讲过了存储后端的跨云支持,现在让我们将目光聚焦到数据的管理。更具体来说,数据在 Databend 工作流中的流入与流出。

+

COPY INTO,数据载入

+

要讲数据管理,就不得不讨论数据从哪里来。过去可能还需要考虑是否需要迁移存储服务,但现在,你可以从数十种 Databend 支持或兼容的存储服务中加载数据,一切都显得那么自然。

+

COPY INTO 语句是窥探 Databend 跨云能力的一个窗口,下面的示例展示了如何从 Azure Blob 加载数据到 Databend 之中。

+
COPY INTO mytable
+    FROM 'azblob://mybucket/data.csv'
+    CONNECTION = (
+        ENDPOINT_URL = 'https://<account_name>.blob.core.windows.net'
+        ACCOUNT_NAME = '<account_name>'
+        ACCOUNT_KEY = '<account_key>'
+    )
+    FILE_FORMAT = (type = CSV);
+
+

当然,不止是 Azure Blob ,Databend 支持的其他云对象存储服务、IPFS 以及可以经由 HTTPS 访问的文件都可以作为 External location ,通过 COPY INTO 语句加载进来。

+
+

Databend 的 COPY INTO 语句还支持进行基本的转换服务,可以减轻 ETL 工作的负担。

+
+

Stage ,数据暂存区

+

刚刚提到 External location ,事实上,要加载到 Databend 中的数据文件还可以在 Stage 中暂存。Databend 同样支持 Internal stage 和 Named external stage 。

+

数据文件可以经由 PUT_INTO_STAGE API 上传到 Internal Stage,由 Databend 交付当前配置的存储后端进行统一管理。而 Named external stage 则可以用于挂载其他 Databend 支持的多种存储服务之中的 bucket 。

+

下面的例子展示了如何在 Databend 中创建一个名为 whdfs 的 Stage ,通过 WebHDFS 协议将 HDFS 中 data-files 目录下的数据文件导入 Databend 。

+
bendsql> CREATE STAGE IF NOT EXISTS whdfs URL='webhdfs://127.0.0.1:9870/data-files/' CONNECTION=(HTTPS='false');
+Query OK, 0 rows affected (0.01 sec)
+
+bendsql> COPY INTO books FROM @whdfs FILES=('books.csv') file_format=(type=CSV field_delimiter=','  record_delimiter='\n' skip_header=0);
+Query OK, 2 rows affected (1.83 sec)
+
+
+

如果你并不想直接导入数据,也可以尝试 SELECT FROM STAGE ,快速分析位于暂存区中的数据文件。

+
+

Catalog,数据挂载

+

放在对象存储中的数据加载得到了解决,还有一个值得思考的问题是,如果数据原本由其他数据分析系统所管理,该怎么办?

+

Databend 提供多源数据目录(Multiple Catalog)的支持,允许挂载 Hive 、Iceberg 等外部数据目录。

+

下面的示例展示如何利用配置文件挂载 Hive 数据目录。

+
[catalogs.hive]
+type = "hive"
+# hive metastore address, such as 127.0.0.1:9083
+address = "<hive-metastore-address>"
+
+

除了挂载,查询也是小菜一碟 select * from hive.$db.$table limit 10;

+

当然,这一切也可以通过 CREATE CATALOG 语句轻松搞定,下面的例子展示了如何挂载 Iceberg 数据目录。

+
CREATE CATALOG iceberg_ctl
+  TYPE=ICEBERG
+  CONNECTION=( 
+    URL="s3://my_bucket/path/to/db" 
+    AWS_KEY_ID="<access-key>"
+    AWS_SECRET_KEY="<secret_key>"
+    SESSION_TOKEN="<session_token>"
+  );
+
+
+

Multiple Catalog 相关的能力还在积极开发迭代中,感兴趣的话可以保持关注。

+
+

再探 COPY INTO,数据导出

+

数据导出是数据管理中的另外一个重要话题,简单来讲,就是转储查询结果以供进一步的分析和处理。

+

这一能力同样由 COPY INTO 语法提供支持,当然,同样支持数十种存储服务和多种文件输出格式。下面的示例展示了如何将查询结果以 CSV 格式文件的形式导出到指定 Stage 中。

+
-- Unload the data from a query into a CSV file on the stage
+COPY INTO @s2 FROM (SELECT name, age, id FROM test_table LIMIT 100) FILE_FORMAT = (TYPE = CSV);
+
+

这一语法同样支持导出到 External location ,真正做到数据的自由流动。

+
+

Databend 还支持 PRESIGN ,用来为 Stage 中的文件生成预签名的 URL ,用户可以通过 Web 浏览器或 API 请求自由访问该文件。

+
+

数据共享

+

刚才提到的 Databend 数据管理环节跨云主要是指 Databend 与外部服务之间的交互。此外,Databend 实例之间也可以经由多种云存储服务来支持数据共享。

+

为了更好地满足多云环境下的数据库查询需求,Databend 设计并实现了一套 RESTful API 来支持数据共享。

+

+

(上图所示为数据共享的工作流)

+

通过在配置文件中添加 share_endpoint_address 相关配置,用户可以利用预先部署好的 open-sharing 服务,经由熟悉的云存储服务共享 Databend 管理的数据库或表。

+
CREATE SHARE myshare;
+GRANT USAGE ON DATABASE db1 TO SHARE myshare;
+GRANT SELECT ON TABLE db1.table1 TO SHARE myshare;
+ALTER SHARE myshare ADD TENANTS = vendor;
+
+

此时,表 db1.table1 将对接受方租户 vendor 可见,并能够进行必要的查询。

+
CREATE DATABASE db2 FROM SHARE myshare;
+SELECT * FROM db2.table1;
+
+

跨云的未来

+

上面的几个视角,只是展示 Databend 在天空计算道路上的一个小小侧影。

+

数据合规、隐私保护等内容同样是我们所关心的重要议题。

+

Databend 的愿景是成为未来跨云分析的基石,让数据分析变得更加简单、快速、便捷和智能。

+

总结

+

本文介绍了天空计算的概念和背景,以及 Databend 的跨云数据存储和访问。

+

天空计算是一种将公有云、私有云和边缘设备统一起来的方法,目标是提供一种无缝的 API 和生态体系,使得用户可以在不同的云之间自由地迁移应用程序和数据。

+

Databend 是一个开源的、完全面向云架构的新式数仓,它采用存算分离的架构,并抽象出一套统一的数据访问层(OpenDAL),从而屏蔽了不同云服务之间的 API 兼容性问题。Databend 可以满足用户在不同的云之间自由地访问数据并进行查询,而不必担心兼容性和迁移成本的问题。同时,Databend 还可以提供更高效、更安全、更可靠的计算服务,从而满足用户对于云计算的不断增长的需求。

+

欢迎部署 Databend 或者访问 Databend Cloud ,即刻探索天空计算的无尽魅力。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 分布式 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Databend 源码阅读: 开篇 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/source-reading/config/index.html b/docs/source-reading/config/index.html new file mode 100644 index 0000000..18cfc8f --- /dev/null +++ b/docs/source-reading/config/index.html @@ -0,0 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 源码阅读:配置管理 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 源码阅读:配置管理

+ +

对于 Databend 这样复杂的数据库服务端程序,往往需要支持大量的可配置选项,以帮助运维人员根据实际使用需要管理和调优系统。

+

Databend 目前支持三种配置方式:命令行、环境变量和配置文件,优先级依次递减。

+
    +
  • 一般情况下,推荐使用配置文件来记录和管理各种配置。
  • +
  • 对于 K8S 集群,为了灵活变更部分配置(比如,特性开关),使用环境变量可能是更优雅的形式。
  • +
  • 命令行则用于调整本地环境下的少数冲突配置。
  • +
+

Databend Query 中的映射

+

对于 databend-query ,不管是什么形式的配置,其配置选项几乎可以看作是代码的扁平化树形映射,即基本符合代码中「配置域」+「配置项」的逻辑。

+
    +
  • 环境变量和配置文件中,利用 serfig 将代码嵌套展开,使用 _ 做为分隔符。
  • +
  • 命令行中稍有不同:一方面,分隔符使用 -;另一方面,部分命令行选项的名称中没有绑定配置域。
  • +
+

为了更好理解这里的映射关系,我们可以深入到具体一项配置,下面将围绕 admin_api_address 这个配置项展开。

+
    +
  • +

    在环境变量上,需要使用 QUERY_ADMIN_API_ADDRESSQUERY 表征这个配置所处的域,而 ADMIN_API_ADDRESS 是具体的配置项。

    +
  • +
  • +

    在配置文件中,通常是使用 toml 来进行配置。 [query] 表征配置所处的域,admin_api_address 为具体的配置项。

    +
    [query]
    +...
    +# Databend Query http address.
    +# For admin RESET API.
    +admin_api_address = "0.0.0.0:8081"
    +...
    +
    +
  • +
  • +

    命令行中需要使用 --admin-api-address 进行配置,这一项没有绑定「配置域」。如果是配置 --storage-s3-access-key-id ,那么「storage」+ 「s3」构成配置域,「access-key-id」是具体的配置项。

    +
  • +
+

在了解如何对 admin_api_address 进行配置后,让我们进入到配置相关的代码,进一步查看映射关系的代码形式(位于 src/query/config/src/config.rs)。

+
pub struct Config {
+    ...
+
+    // Query engine config.
+    #[clap(flatten)]
+    pub query: QueryConfig,
+    
+    ...
+}
+
+/// Query config group.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Args)]
+#[serde(default, deny_unknown_fields)]
+pub struct QueryConfig {
+    ...
+    
+    #[clap(long, default_value = "127.0.0.1:8080")]
+    pub admin_api_address: String,
+    
+    ...
+}
+
+

因为代码中使用了嵌套的层级结构,最上层是 Config,而 admin_api_addresspub query: QueryConfig 中的一个配置项,经过 serfig 处理后,需要使用 QUERY 或者 [query] 表征其所处的域,配置项就还是 admin_api_address

+

而命令行中具体的配置项名称和默认值会受到 #[clap(long = "<long-name>", default_value = "<value>")] 控制),clap 会接管配置:

+
    +
  • admin_api_address 就变成了 --admin-api-address
  • +
  • --storage-s3-access-key-id 而言,其实际的代码层级是 Config -> StorageConfig -> S3StorageConfig -> access_key_id,字段之上有标注 #[clap(long = "storage-s3-access-key-id", default_value_t)] ,所以需要使用 --storage-s3-access-key-id 进行配置。
  • +
+

Databend Meta 中的映射

+

databend-meta 的配置文件和命令行逻辑与 databend-query 是基本一致的。但是环境变量是通过 serfig 内置的 serde-env 自行定义的映射关系(但同样可以尝试按「配置域」+「配置项」进行理解)。

+

同样具体到单独的某项配置来看一下,这里以 log_dir 为例。

+
    +
  • +

    在环境变量上,需要使用 METASRV_LOG_DIRMETASRV 表征这个配置所处的域,而 LOG_DIR 是具体的配置项。

    +
  • +
  • +

    而在配置文件中,这一配置项作用于全局,只需要:

    +
    log_dir                 = "./.databend/logs1"
    +
    +
  • +
  • +

    在命令行中当然也直接 --log-dir 进行配置。

    +
  • +
+

让我们通过代码来解构其映射,代码位于 src/meta/service/src/configs/outer_v0.rs

+
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Parser)]
+#[clap(about, version = &**METASRV_COMMIT_VERSION, author)]
+#[serde(default)]
+pub struct Config {
+    ...
+    /// Log file dir
+    #[clap(long = "log-dir", default_value = "./.databend/logs")]
+    pub log_dir: String,
+    ...
+}
+
+

配置文件和命令行参数相关的配置项是由 Config 结构体管理的,逻辑与 databend-query 一致,就不再赘述。

+

而环境变量的配置项是由 ConfigViaEnv 结构体进行处理的,如下:

+
/// #[serde(flatten)] doesn't work correctly for env.
+/// We should work around it by flatten them manually.
+/// We are seeking for better solutions.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[serde(default)]
+pub struct ConfigViaEnv {
+    ...
+    pub metasrv_log_dir: String,
+    ...
+}
+
+

Config 之间的映射关系位于 impl From<Config> for ConfigViaEnvimpl Into<Config> for ConfigViaEnv 这两个部分。对于 metasrv_log_dir 而言,就是映射到前面的 log_dir 字段。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 多云转晴:Databend 的天空计算之路 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 第一弹 - minibend 简介 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/source-reading/index.html b/docs/source-reading/index.html new file mode 100644 index 0000000..4a9531e --- /dev/null +++ b/docs/source-reading/index.html @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +源码解读 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+ +
+
+ + + + + + + + + + + + + + diff --git a/docs/source-reading/init-session-handler/index.html b/docs/source-reading/init-session-handler/index.html new file mode 100644 index 0000000..8d3c43f --- /dev/null +++ b/docs/source-reading/init-session-handler/index.html @@ -0,0 +1,949 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 源码阅读: Query Server 启动、Session 管理及请求处理 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 源码阅读: Query Server 启动、Session 管理及请求处理

+ +

作者:AriesDevil | Databend Labs 成员,数据库研发工程师

+

Query 启动入口

+

Databend Query Server 的启动入口在 databend/src/binaries/query/main.rs 下,在初始化配置之后,它会创建一个 GlobalServices 和 server 关闭时负责处理 shutdown 逻辑的 shutdown_handle

+
GlobalServices::init(conf.clone()).await?;
+let mut shutdown_handle = ShutdownHandle::create()?;
+
+

GlobalServices

+

GlobalServices 负责启动 databend-query 的所有全局服务,这些服务都遵循单一责任原则。

+
pub struct GlobalServices {
+    global_runtime: UnsafeCell<Option<Arc<Runtime>>>,
+    // 负责处理 query log
+    query_logger: UnsafeCell<Option<Arc<QueryLogger>>>,
+    // 负责 databend query 集群发现
+    cluster_discovery: UnsafeCell<Option<Arc<ClusterDiscovery>>>,
+    // 负责与 storage 层交互来读写数据
+    storage_operator: UnsafeCell<Option<Operator>>,
+    async_insert_manager: UnsafeCell<Option<Arc<AsyncInsertManager>>>,
+    cache_manager: UnsafeCell<Option<Arc<CacheManager>>>,
+    catalog_manager: UnsafeCell<Option<Arc<CatalogManager>>>,
+    http_query_manager: UnsafeCell<Option<Arc<HttpQueryManager>>>,
+    data_exchange_manager: UnsafeCell<Option<Arc<DataExchangeManager>>>,
+    session_manager: UnsafeCell<Option<Arc<SessionManager>>>,
+    users_manager: UnsafeCell<Option<Arc<UserApiProvider>>>,
+    users_role_manager: UnsafeCell<Option<Arc<RoleCacheManager>>>,
+}
+
+

GlobalServices 中的全局服务都实现了单例 trait,这些全局管理器后续会有对应的源码分析文章介绍,本文介绍与 Session 处理相关的逻辑。

+
pub trait SingletonImpl<T>: Send + Sync {
+    fn get(&self) -> T;
+
+    fn init(&self, value: T) -> Result<()>;
+}
+
+pub type Singleton<T> = Arc<dyn SingletonImpl<T>>;
+
+

ShutdownHandle

+

接下来会根据网络协议初始化 handlers,并把它们注册到 shutdown_handler 的 services 中,任何实现 Server trait 的类型都可以被添加到 services 中。

+

query server

+
#[async_trait::async_trait]
+pub trait Server: Send {
+    async fn shutdown(&mut self, graceful: bool);
+    async fn start(&mut self, listening: SocketAddr) -> Result<SocketAddr>;
+}
+
+

目前 Databend 支持三种协议提交查询请求:MySql, ClickHouse HTTP, Raw HTTP 。

+
// MySQL handler.
+{
+    let hostname = conf.query.mysql_handler_host.clone();
+    let listening = format!("{}:{}", hostname, conf.query.mysql_handler_port);
+    let mut handler = MySQLHandler::create(session_manager.clone());
+    let listening = handler.start(listening.parse()?).await?;
+    // 注册服务到 shutdown_handle 来处理 server shutdown 时候的关闭逻辑,下同
+    shutdown_handle.add_service(handler);
+}
+
+// ClickHouse HTTP handler.
+{
+    let hostname = conf.query.clickhouse_http_handler_host.clone();
+    let listening = format!("{}:{}", hostname, conf.query.clickhouse_http_handler_port);
+
+    let mut srv = HttpHandler::create(session_manager.clone(), HttpHandlerKind::Clickhouse);
+    let listening = srv.start(listening.parse()?).await?;
+    shutdown_handle.add_service(srv);
+}
+
+// Databend HTTP handler.
+{
+    let hostname = conf.query.http_handler_host.clone();
+    let listening = format!("{}:{}", hostname, conf.query.http_handler_port);
+
+    let mut srv = HttpHandler::create(session_manager.clone(), HttpHandlerKind::Query);
+    let listening = srv.start(listening.parse()?).await?;
+    shutdown_handle.add_service(srv);
+}
+
+

之后会创建一些其它服务:

+
    +
  • Metric Service :指标服务。
  • +
  • Admin Service :负责处理管理信息。
  • +
  • RPC Service :query 节点的 rpc 服务,负责 query 节点之间的通信,使用 arrow flight 协议。
  • +
+
// Metric API service.
+{
+    let address = conf.query.metric_api_address.clone();
+    let mut srv = MetricService::create(session_manager.clone());
+    let listening = srv.start(address.parse()?).await?;
+    shutdown_handle.add_service(srv);
+    info!("Listening for Metric API: {}/metrics", listening);
+}
+
+// Admin HTTP API service.
+{
+    let address = conf.query.admin_api_address.clone();
+    let mut srv = HttpService::create(session_manager.clone());
+    let listening = srv.start(address.parse()?).await?;
+    shutdown_handle.add_service(srv);
+    info!("Listening for Admin HTTP API: {}", listening);
+}
+
+// RPC API service.
+{
+    let address = conf.query.flight_api_address.clone();
+    let mut srv = RpcService::create(session_manager.clone());
+    let listening = srv.start(address.parse()?).await?;
+    shutdown_handle.add_service(srv);
+    info!("Listening for RPC API (interserver): {}", listening);
+}
+
+

最后会将这个 query 节点注册到 meta server 中。

+
// Cluster register.
+{
+    let cluster_discovery = session_manager.get_cluster_discovery();
+    let register_to_metastore = cluster_discovery.register_to_metastore(&conf);
+    register_to_metastore.await?;
+}
+
+

Session 相关

+

session 主要分为 4 个部分:

+
    +
  • session_manager :全局唯一,负责管理 client session 。
  • +
  • session :每当有新的 client 连接到 server 之后会创建一个新的 session 并且注册到 session_manager
  • +
  • query_ctx :每一条查询语句会有一个 query_ctx,用来存储当前查询的一些上下文信息 。
  • +
  • query_ctx_shared :查询语句中的子查询共享的上下文信息 。
  • +
+

session

+

下面逐一来分析。

+

SessionManager

+

代码位置:query/src/sessions/session_mgr.rs

+
pub struct SessionManager {
+    pub(in crate::sessions) conf: Config,
+    pub(in crate::sessions) max_sessions: usize,
+    pub(in crate::sessions) active_sessions: Arc<RwLock<HashMap<String, Arc<Session>>>>,
+    pub status: Arc<RwLock<SessionManagerStatus>>,
+
+    // When session type is MySQL, insert into this map, key is id, val is MySQL connection id.
+    pub(crate) mysql_conn_map: Arc<RwLock<HashMap<Option<u32>, String>>>,
+    pub(in crate::sessions) mysql_basic_conn_id: AtomicU32,
+}
+
+

SessionManager 主要用来创建和销毁 session,对应方法如下:

+
// 根据 client 协议类型来创建 session
+pub async fn create_session(self: &Arc<Self>, typ: SessionType) -> Result<SessionRef>
+
+// 根据 session id 来销毁 session
+pub fn destroy_session(self: &Arc<Self>, session_id: &String)
+
+

Session

+

代码位置:query/src/sessions/session.rs

+

session 主要存储 client-server 的上下文信息,代码命名已经很清晰了,这里就不再过多赘述。

+
pub struct Session {
+    pub(in crate::sessions) id: String,
+    pub(in crate::sessions) typ: RwLock<SessionType>,
+    pub(in crate::sessions) session_ctx: Arc<SessionContext>,
+    status: Arc<RwLock<SessionStatus>>,
+    pub(in crate::sessions) mysql_connection_id: Option<u32>,
+}
+
+pub struct SessionContext {
+    conf: Config,
+    abort: AtomicBool,
+    current_catalog: RwLock<String>,
+    current_database: RwLock<String>,
+    current_tenant: RwLock<String>,
+    current_user: RwLock<Option<UserInfo>>,
+    auth_role: RwLock<Option<String>>,
+    client_host: RwLock<Option<SocketAddr>>,
+    io_shutdown_tx: RwLock<Option<Sender<Sender<()>>>>,
+    query_context_shared: RwLock<Option<Arc<QueryContextShared>>>,
+}
+
+pub struct SessionStatus {
+    pub session_started_at: Instant,
+    pub last_query_finished_at: Option<Instant>,
+}
+
+

Session 的另一个大的功能是负责创建和获取 QueryContext,每次接收到新的 query 请求都会创建一个 QueryContext 并绑定在对应的 query 语句上。

+

QueryContext

+

代码位置:query/src/sessions/query_ctx.rs

+

QueryContext 主要是维护查询的上下文信息,它通过 QueryContext::create_from_shared (query_ctx_shared) 创建。

+
#[derive(Clone)]
+pub struct QueryContext {
+    version: String,
+    statistics: Arc<RwLock<Statistics>>,
+    partition_queue: Arc<RwLock<VecDeque<PartInfoPtr>>>,
+    shared: Arc<QueryContextShared>,
+    precommit_blocks: Arc<RwLock<Vec<DataBlock>>>,
+    fragment_id: Arc<AtomicUsize>,
+}
+
+

其中 partition_queue 主要存储查询对应的 PartInfo,包括 part 的地址、版本信息、涉及数据的行数,part 使用的压缩算法、以及涉及到 column 的 meta 信息。在 pipeline build 时候会去设置 partition 。pipeline 后续会有专门的文章介绍。

+

precommit_blocks 负责暂存插入操作的时已经写入到存储, 但是尚未提交的元数据,DataBlock 主要包含 Column 的元信息引用和 arrow schema 的信息。

+

QueryContextShared

+

代码位置:query/src/sessions/query_ctx_shared.rs

+

对于包含子查询的查询,需要共享很多上下文信息,这就是 QueryContextShared 存在的理由。

+
/// 数据需要在查询上下文中被共享,这个很重要,比如:
+///     USE database_1;
+///     SELECT
+///         (SELECT scalar FROM table_name_1) AS scalar_1,
+///         (SELECT scalar FROM table_name_2) AS scalar_2,
+///         (SELECT scalar FROM table_name_3) AS scalar_3
+///     FROM table_name_4;
+/// 对于上面子查询, 会共享 runtime, session, progress, init_query_id
+pub struct QueryContextShared {
+    /// scan_progress for scan metrics of datablocks (uncompressed)
+    pub(in crate::sessions) scan_progress: Arc<Progress>,
+    /// write_progress for write/commit metrics of datablocks (uncompressed)
+    pub(in crate::sessions) write_progress: Arc<Progress>,
+    /// result_progress for metrics of result datablocks (uncompressed)
+    pub(in crate::sessions) result_progress: Arc<Progress>,
+    pub(in crate::sessions) error: Arc<Mutex<Option<ErrorCode>>>,
+    pub(in crate::sessions) session: Arc<Session>,
+    pub(in crate::sessions) runtime: Arc<RwLock<Option<Arc<Runtime>>>>,
+    pub(in crate::sessions) init_query_id: Arc<RwLock<String>>,
+    ...
+}
+
+

它提供了 query 上下文所需要的一切基本信息。

+

Handler

+

之前提到了 Databend 支持多种 handler,下面就以 mysql 为例,看一下 handler 的处理流程以及如何与 session 产生交互。

+

首先 MySQLHandler 会包含一个 SessionManager 的引用。

+
pub struct MySQLHandler {
+    abort_handle: AbortHandle,
+    abort_registration: Option<AbortRegistration>,
+    join_handle: Option<JoinHandle<()>>,
+}
+
+

MySQLHandler 在启动后,会 spawn 一个 tokio task 来持续监听 tcp stream,并且创建一个 session 再启动一个 task 去执行之后的查询请求。

+
fn accept_socket(session_mgr: Arc<SessionManager>, executor: Arc<Runtime>, socket: TcpStream) {
+    executor.spawn(async move {
+        // 创建 session
+        match session_mgr.create_session(SessionType::MySQL).await {
+            Err(error) => Self::reject_session(socket, error).await,
+            Ok(session) => {
+                info!("MySQL connection coming: {:?}", socket.peer_addr());
+                // 执行查询
+                if let Err(error) = MySQLConnection::run_on_stream(session, socket) {
+                    error!("Unexpected error occurred during query: {:?}", error);
+                };
+            }
+        }
+    });
+}
+
+

MySQLConnection::run_on_stream 中,session 会先 attach 到对应的 client host 并且注册一个 shutdown 闭包来处理关闭连接关闭时需要执行的清理,关键代码如下:

+
// mysql_session.rs
+pub fn run_on_stream(session: SessionRef, stream: TcpStream) -> Result<()> {
+    let blocking_stream = Self::convert_stream(stream)?;
+    MySQLConnection::attach_session(&session, &blocking_stream)?;
+
+    ...
+}
+
+fn attach_session(session: &SessionRef, blocking_stream: &std::net::TcpStream) -> Result<()> {
+    let host = blocking_stream.peer_addr().ok();
+    let blocking_stream_ref = blocking_stream.try_clone()?;
+    session.attach(host, move || {
+        // 注册 shutdown 逻辑
+        if let Err(error) = blocking_stream_ref.shutdown(Shutdown::Both) {
+            error!("Cannot shutdown MySQL session io {}", error);
+        }
+    });
+
+    Ok(())
+}
+
+// session.rs
+pub fn attach<F>(self: &Arc<Self>, host: Option<SocketAddr>, io_shutdown: F)
+where F: FnOnce() + Send + 'static {
+    let (tx, rx) = oneshot::channel();
+    self.session_ctx.set_client_host(host);
+    self.session_ctx.set_io_shutdown_tx(Some(tx));
+
+    common_base::base::tokio::spawn(async move {
+        // 在 session quit 时候触发清理
+        if let Ok(tx) = rx.await {
+            (io_shutdown)();
+            tx.send(()).ok();
+        }
+    });
+}
+
+

之后会启动一个 MySQL InteractiveWorker 来处理后续的查询。

+
let join_handle = query_executor.spawn(async move {
+    let client_addr = non_blocking_stream.peer_addr().unwrap().to_string();
+    let interactive_worker = InteractiveWorker::create(session, client_addr);
+    let opts = IntermediaryOptions {
+        process_use_statement_on_query: true,
+    };
+    let (r, w) = non_blocking_stream.into_split();
+    let w = BufWriter::with_capacity(DEFAULT_RESULT_SET_WRITE_BUFFER_SIZE, w);
+    AsyncMysqlIntermediary::run_with_options(interactive_worker, r, w, &opts).await
+});
+let _ = futures::executor::block_on(join_handle);
+
+

InteractiveWorker 会实现 AsyncMysqlShim trait 的方法,比如:on_executeon_query 等。查询到来时会回调这些方法来执行查询。

+

这里以 on_query 为例,关键代码如下:

+
async fn on_query<'a>(
+    &'a mut self,
+    query: &'a str,
+    writer: QueryResultWriter<'a, W>,
+) -> Result<()> {
+    ...
+
+    // response writer
+    let mut writer = DFQueryResultWriter::create(writer);
+
+    let instant = Instant::now();
+    // 执行查询
+    let blocks = self.base.do_query(query).await;
+
+    // 回写结果
+    let format = self.base.session.get_format_settings()?;
+    let mut write_result = writer.write(blocks, &format);
+
+    ...
+
+    // metrics 信息
+    histogram!(
+        super::mysql_metrics::METRIC_MYSQL_PROCESSOR_REQUEST_DURATION,
+        instant.elapsed()
+    );
+
+    write_result
+}
+
+

do_query 中会创建 QueryContext 并开始解析 sql 流程来完成后续的整个 sql 查询。关键代码如下:

+
// 创建 QueryContext
+let context = self.session.create_query_context().await?;
+// 关联到查询语句
+context.attach_query_str(query);
+
+let settings = context.get_settings();
+
+// parse sql
+let stmts_hints = DfParser::parse_sql(query, context.get_current_session().get_type());
+...
+
+// 创建并生成查询计划
+let mut planner = Planner::new(context.clone());
+let interpreter = planner.plan_sql(query).await.and_then(|v| {
+    has_result_set = has_result_set_by_plan(&v.0);
+    InterpreterFactoryV2::get(context.clone(), &v.0)
+})
+
+// 执行查询,返回结果
+Self::exec_query(interpreter.clone(), &context).await?;
+let schema = interpreter.schema();
+Ok(QueryResult::create(
+    blocks,
+    extra_info,
+    has_result_set,
+    schema,
+))
+
+

尾声

+

以上就是从 Databend 启动服务到接受 SQL 请求并开始处理的流程。最近我们因为一些原因(ClickHouse TCP 协议偏向 ClickHouse 的底层,协议没有公开的文档说明,同时里面历史包袱比较重,排查问题浪费大量精力)去掉了 ClickHouse Native TCP Client,具体请参见: https://github.com/datafuselabs/databend/pull/7012

+

如果你阅读完代码有好的提议,欢迎来这里讨论,另外如果发现相关的问题,可以提交到 issue 来帮助我们提高 Databend 的稳定性。Databend 社区欢迎一切善意的意见和建议 :)

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 多云转晴:Databend 的天空计算之路 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 第一弹 - minibend 简介 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/source-reading/intro/index.html b/docs/source-reading/intro/index.html new file mode 100644 index 0000000..ef6434a --- /dev/null +++ b/docs/source-reading/intro/index.html @@ -0,0 +1,661 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 源码阅读: 开篇 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 源码阅读: 开篇

+ +

作者:sundy-li | Databend Labs 成员,折腾过 Clickhouse

+

前言

+

Databend 在 2021 年开源后,陆续受到了很多社区同学的关注。Databend 使用了 Rust 编程语言。为了吸引更多的开发者,特别是没有 Rust 开发经验的新同志,我们设计了 Rust 相关课程,同时建立了多个 Rust 兴趣小组。

+

Databend 在 issue 中还引入了“Good First issue”的 label 来引导社区新同学参与第一次贡献,目共有超过一百多位 contributors,算是一个不错的成果。

+

但 Databend 也在过去的一年中经历了数次迭代,代码日渐复杂。目前代码主干分支有 26 w 行 rust 代码,46 个 crate,对于新接触 Databend 的技术爱好者来说,贡献门槛越来越高。即使是熟悉 rust 的同学,clone 代码后,面对着茫茫码海,竟不知如何读起。在多个社区群中,也有朋友数次提到什么时候能有一个 Databend 源码阅读系列文章,帮助大家更快熟悉 Databend 代码。

+

因此,我们接下来会开展“Databend 源码阅读”系列文章,主要受众是社区技术开发者,希望通过源码阅读,来加强和社区的技术交流,引发更多思维碰撞。

+

Databend 的故事

+

很多同学都问过我们一个问题:为什么你们要用 Rust 从零构建一个数据库?其实这个问题可以分为两个子问题:

+

为什么选择的是 Rust?

+

我们早期的成员大多是 ClickHouse、tidb 、tokudb 等知名数据库的贡献者,从技术栈来说更熟悉的是 C++ 和 Go。虎哥(@bohutang)在疫情期间也使用 Go 实现了一个小的数据库原型 vectorsql,有同学表示 vectorsql 的架构非常优雅,值得学习借鉴。

+

vectorsql

+

语言本没有孰劣之分,要从面向的场景来聊聊。目前大多的 DMBS 使用的是 C++/Java,新型的 NewSQL 更多使用的是 Go。在以往的开发经验来看,C/C++ 已经是高性能的代名词,开发者更容易写出高运行效率的代码,但 C++ 的开发效率实在不忍直视,工具链不是很完善,开发者很难一次性写出内存安全,并发安全的代码。而 Go 可能是另外一个极端,大道至简,工具链完善,开发效率非常高,不足之处在于泛型的进度太慢了,在 DB 系统上内存不能很灵活的控制,且难于达到前者的运行性能,尤其使用 SIMD 指令还需要和汇编代码交互等。我们需要的是兼具 开发效率(内存安全,并发安全,工具链完善)& 运行效率 的语言,当时看来,Rust 可能是我们唯一的选择了,历经尝试后,我们也发现,Rust 不仅能满足我们的需求,而且很酷!

+

为什么要从零构建一个数据库系统?

+

总体来说,路线无非就以下两条:

+
    +
  • +

    基于知名的开源数据库做二次开发优化

    +

    这条路线可能更多人会选择,因为有一个好的数据库底座,无需再做一些重复性的工作,在上面做二次开发的话能省不少力气,团队专注做优化改进重构,能更早推动版本,落地商业化。缺点是 fork 后的版本难于再次回馈到社区,相当于另外一套独立的系统,如 PG 下的各个子流派。

    +
  • +
  • +

    从零构建一套新的数据库系统

    +

    这条路线走起来比较艰难,因为数据库系统实在太庞大了,一个子方向都足够专业人士深入研究十几年。这个方向虽然没能直接站在已有的底座上,但会让设计者更加灵活可控,无需关注太多历史的包袱。Databend 在设计之初面向的是云原生数仓的场景,和传统的数据库系统有很大的区别,如果基于传统数据库系统来做,改造代码的成本和从零做的成本可能差不多,因此我们选择的是这条路来从零打造一个全新的云数仓。

    +
  • +
+

Databend 的架构

+

画虎画皮难画骨,我们先从 Databend 的“骨”聊起。

+

databend arch

+

虽然我们是使用 Rust 从零开始实现的,但不是完全闭门造轮子,一些优秀的开源组件或者生态也有在其中集成。如:我们兼容了 Ansi-SQL 标准,提供了 MySQL/ClickHouse 等主流协议的支持,拥抱了万物互联的 Arrow 生态,存储格式基于大数据主流的 Parquet 格式等。我们不仅会积极地回馈了贡献给上游,如 Arrow2/Tokio 等开源库,一些通用的组件我们也抽成独立的项目开源在Github(openraft, opendal, opencache, opensrv 等)。

+

Databend 定义为云原生的弹性数据库,在设计之初我们不仅要做到计算存储分离,每一层的极致的弹性都是设计主要考量点。Databend 主要分为三层:MetaService Layer,Query Layer,Storage Layer,这三层都是可以弹性扩展的,意味着用户可以为自己的业务选择最适合的集群规模,并且随着业务发展来伸缩集群。

+

下面我们将从这三层来介绍下 Databend 的主要代码模块。

+

Databend 的模块

+

MetaService Layer

+

MetaService 主要用于存储读取持久化的元数据信息,比如 Catalogs/Users 等。

+ + + + + + + + +
包名作用
meta/serviceMetaService 服务,作为独立进程部署,可部署多个组成集群,底层使用 Raft 做分布式共识,Query 以 Grpc 和 MetaService 交互。
meta/types定义了各类需要保存在 MetaService 的结构体,由于这些结构体最终需要持久化,所以涉及到数据序列化的问题,当前使用 Protobuf 格式来进行序列化和反序列化操作,这些类型相关的 Rust 结构体与 Protobuf 的相互序列化规则代码定义在 meta/proto-conv 子目录中。
meta/sled-store当前 MetaService 使用 sled 来保存持久化数据,这个子目录封装了 sled 相关的操作接口。
meta/raft-storeopenraft 用户层需要实现 raft store 的存储接口用于保存数据,这个子目录就是 MetaService 实现的 openraft 的存储层,底层依赖于 sled 存储,同时这里还实现了 openraft 用户层需要自定义的状态机。
meta/api对 query 暴露的基于 KVApi 实现的用户层 api 接口。
meta/grpc基于 grpc 封装的 client,MetaService 的客户端使用这里封装好的 client 与 MetaService 进行通信交互。
rafthttps://github.com/datafuselabs/openraft,从 async-raft 项目中衍生改进的全异步 Raft 库。
+

Query Layer

+

Query 节点主要用于计算,多个 query 节点可以组成 MPP 集群,理论上性能会随着 query 节点数水平扩展。SQL 在 query 中会经历以下几个转换过程:

+

query

+

从 SQL 字符串经过 Parser 解析成 AST 语法树,然后经过 Binder 绑定 catalog 等信息转成逻辑计划,再经过一系列优化器处理转成物理计划,最后遍历物理计划构建对应的执行逻辑。 +query 涉及的模块有:

+ + + + + + + + + + + + + + + + + + + + + +
包名作用
query/serviceQuery 服务,整个函数的入口在 bin/databend-query.rs 其中包含一些子模块,这里介绍下比较重要的子模块
api :对外暴露给外部的 HTTP/RPC 接口
catalogs:catalogs 管理,目前支持默认的 catalog(存储在 metaservice)以及 hive catalog (存储在 hive meta store)
clusters:query 集群信息
databases:query 支持的 database engine 相关
evaluator:表达式计算工具类
interpreters:SQL 执行器,SQL 构建出 Plan 后,通过对应执行器去做物理执行
pipelines:实现了物理算子的调度框架
servers:对外暴露的服务,有 clickhouse/mysql/http 等
sessions:session 管理相关
sql:包含新的 planner 设计,新的 binder 逻辑,新的 optimizers 设计
table_functions:表函数相关,如 numbers
query/ast基于 nom_rule 实现的新版 sql parser
query/datavalues各类 Column 的定义,表示数据在内存上的布局, 后续会逐步迁移到 query/expressions
query/datablocksDatablock 表示 Vec<Column> 集合,里面封装了一些常用方法, 后续会逐步迁移到 query/expressions
query/functions标量函数以及聚合函数等实现注册,后续会替换为 query/functions-v2
query/formats负责数据对外各类格式的 序列化反序列化,如 CSV/TSV/Json 格式等
query/storages表引擎相关,最常用为 fuse engine
common/hashtable实现了一个线性探测的 hashtable,主要用于 group by 聚合函数以及 join 等场景
opensrvhttps://github.com/datafuselabs/opensrv
+

Storage Layer

+

Storage 主要涉及表的 Snapshots,Segments 以及索引信息等管理,以及和底层 IO 的交互。Storage 目前一大亮点是基于 Snapshot 隔离 实现了类似 Iceberge 方式的 Increment view, 我们可以对表在任意历史状态下进行 time travel 访问。

+

后续规划

+

源码阅读系列刚刚开始撰写,后续预计将按照介绍各个模块的方式进行逐步讲解,输出主要以文章为主,一些比较重要且有趣的模块设计可能会以视频直播的方式和大家一起交流。

+

目前只是一个初步的规划,在这个过程中会接受大家的建议做一些时间内容调整。无论如何,我们都期待通过这个系列的活动,让更多志同道合的人参与到 Databend 的开发中来,一起学习交流成长。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 多云转晴:Databend 的天空计算之路 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 第一弹 - minibend 简介 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/source-reading/pipeline-execution/index.html b/docs/source-reading/pipeline-execution/index.html new file mode 100644 index 0000000..4ad54ed --- /dev/null +++ b/docs/source-reading/pipeline-execution/index.html @@ -0,0 +1,1676 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 源码阅读: pipeline 的执行 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 源码阅读: pipeline 的执行

+ +

作者:Dousir9 | Databend Contributor

+

一条 SQL 的 pipeline

+

本篇文章将以一条 SQL select t.id from t group by t.id 为例,分析 Pipeline 的执行,表结构及该 SQL 的 pipeline 如下所示,我们将从底部的 SyncReadParquetDataSource 向上进行分析。

+
mysql> desc t;
++-------+------+------+---------+-------+
+| Field | Type | Null | Default | Extra |
++-------+------+------+---------+-------+
+| id    | INT  | NO   | 0       |       |
+| val   | INT  | NO   | 0       |       |
++-------+------+------+---------+-------+
+
+mysql> explain pipeline select t.id from t group by t.id;
++--------------------------------------------------------+
+| explain                                                |
++--------------------------------------------------------+
+| CompoundBlockOperator(Project) × 1 processor           |
+|   TransformFinalGroupBy × 1 processor                  |
+|     TransformSpillReader × 1 processor                 |
+|       TransformPartitionBucket × 1 processor           |
+|         TransformGroupBySpillWriter × 1 processor      |
+|           TransformPartialGroupBy × 1 processor        |
+|             DeserializeDataTransform × 1 processor     |
+|               SyncReadParquetDataSource × 1 processor  |
++--------------------------------------------------------+
+
+

execute_single_thread

+

首先我们需要明白 PipelineExecutor 是怎么运作的

+
// src/query/service/src/pipelines/executor/pipeline_executor.rs
+impl PipelineExecutor {
+    // ...
+
+    /// # Safety
+    ///
+    /// Method is thread unsafe and require thread safe call
+    pub unsafe fn execute_single_thread(&self, thread_num: usize) -> Result<()> {
+        let workers_condvar = self.workers_condvar.clone();
+        let mut context = ExecutorWorkerContext::create(
+            thread_num,
+            workers_condvar,
+            self.settings.query_id.clone(),
+        );
+
+        while !self.global_tasks_queue.is_finished() {
+            // When there are not enough tasks, the thread will be blocked, so we need loop check.
+            while !self.global_tasks_queue.is_finished() && !context.has_task() {
+                self.global_tasks_queue.steal_task_to_context(&mut context);
+            }
+
+            while !self.global_tasks_queue.is_finished() && context.has_task() {
+                if let Some(executed_pid) = context.execute_task()? {
+                    // Not scheduled graph if pipeline is finished.
+                    if !self.global_tasks_queue.is_finished() {
+                        // We immediately schedule the processor again.
+                        let schedule_queue = self.graph.schedule_queue(executed_pid)?;
+                        schedule_queue.schedule(&self.global_tasks_queue, &mut context, self);
+                    }
+                }
+            }
+        }
+
+        Ok(())
+    }
+    // ...
+}
+
+

初始化线程

+

在调用 from_pipelines 构建 PipelineExecutor 时,我们会遍历每个 Pipelineget_max_threads 来获得当前这个 PipelineExecutor 所需的线程数 threads_num。然后在 execute_threads 函数中,我们会创建 threads_num 个线程,每个线程都有当前这个 PipelineExecutor 的一份拷贝,随后每个线程会调用 execute_single_thread 开始执行任务。

+

执行

+

(1)首先获得一份条件变量 workers_condvar 的拷贝并用它来创建一个 ExecutorWorkerContext,它存有 query_id,worker_num:worker 编号,task:当前要执行的任务,workers_condvar。

+

(2)当 global_tasks_queue 没有结束时,就会一直循环,如果 context 中没有 task,则会调用 steal_task_to_context 来获取任务,如果没有获取到则阻塞等待被唤醒。

+

(3)当获取到任务时,会首先调用 execute_task 来执行任务,对于 ExecutorTask::Sync 类型的任务来说,会调用 execute_sync_task 进而调用 Processorprocess 函数,然后返回 processor.id() 用来后续推动 pipeline 的执行;而当 task 的类型为 ExecutorTask::AsyncCompleted 时,表示一个异步任务执行完了,这时我们返回 task.id 用来后续推动 pipeline 的执行。

+
// src/query/service/src/pipelines/executor/executor_worker_context.rs
+impl ExecutorWorkerContext {
+    pub unsafe fn execute_task(&mut self) -> Result<Option<NodeIndex>> {
+        match std::mem::replace(&mut self.task, ExecutorTask::None) {
+            ExecutorTask::None => Err(ErrorCode::Internal("Execute none task.")),
+            ExecutorTask::Sync(processor) => self.execute_sync_task(processor),
+            ExecutorTask::AsyncCompleted(task) => match task.res {
+                Ok(_) => Ok(Some(task.id)),
+                Err(cause) => Err(cause),
+            },
+        }
+    }
+}
+
+

(4)在调用 execute_task 后我们得到了一个 executed_pid,这时候我们需要拿这个 executor_pid 来做一些 schedule 工作,继续推动 pipeline 的执行,首先调用 schedule_queue

+
// src/query/service/src/pipelines/executor/executor_graph.rs
+impl ExecutingGraph {
+    // ...
+
+    /// # Safety
+    ///
+    /// Method is thread unsafe and require thread safe call
+    pub unsafe fn schedule_queue(
+        locker: &StateLockGuard,
+        index: NodeIndex,
+        schedule_queue: &mut ScheduleQueue,
+    ) -> Result<()> {
+        let mut need_schedule_nodes = VecDeque::new();
+        let mut need_schedule_edges = VecDeque::new();
+
+        need_schedule_nodes.push_back(index);
+        while !need_schedule_nodes.is_empty() || !need_schedule_edges.is_empty() {
+            // To avoid lock too many times, we will try to cache lock.
+            let mut state_guard_cache = None;
+
+            if need_schedule_nodes.is_empty() {
+                let edge = need_schedule_edges.pop_front().unwrap();
+                let target_index = DirectedEdge::get_target(&edge, &locker.graph)?;
+
+                let node = &locker.graph[target_index];
+                let node_state = node.state.lock().unwrap();
+
+                if matches!(*node_state, State::Idle) {
+                    state_guard_cache = Some(node_state);
+                    need_schedule_nodes.push_back(target_index);
+                }
+            }
+
+            if let Some(schedule_index) = need_schedule_nodes.pop_front() {
+                let node = &locker.graph[schedule_index];
+
+                if state_guard_cache.is_none() {
+                    state_guard_cache = Some(node.state.lock().unwrap());
+                }
+                let event = node.processor.event()?;
+                if tracing::enabled!(tracing::Level::TRACE) {
+                    tracing::trace!(
+                        "node id: {:?}, name: {:?}, event: {:?}",
+                        node.processor.id(),
+                        node.processor.name(),
+                        event
+                    );
+                }
+                let processor_state = match event {
+                    Event::Finished => State::Finished,
+                    Event::NeedData | Event::NeedConsume => State::Idle,
+                    Event::Sync => {
+                        schedule_queue.push_sync(node.processor.clone());
+                        State::Processing
+                    }
+                    Event::Async => {
+                        schedule_queue.push_async(node.processor.clone());
+                        State::Processing
+                    }
+                };
+
+                node.trigger(&mut need_schedule_edges);
+                *state_guard_cache.unwrap() = processor_state;
+            }
+        }
+
+        Ok(())
+    }
+}
+
+

在介绍 schedule_queue 函数之前有几个概念,trait Processoreventprocessasync_process 这些函数,event 的作用是根据当前这个 Processor 的信息,来推动这个 Processor:包括改变 Processor 中的变量,改变 input port 和 output port,event 会返回一个 Event 状态来指示下一步的工作:

+
    +
  • Event::Finished:表示 Processor 的工作结束了,将 Processor 的状态设置为 State::Finished
  • +
  • Event::NeedData | Event::NeedConsume:表示 Processor 的 input 需要数据或者 output 的数据需要被消费,将 Processor 的状态设置为 tate::Idle,表示需要进行 schedule。
  • +
  • Event::Sync:表示 Processor 需要调用 process 进行处理,将 Processor push 到 schedule_queuesync_queue 中,并将 Processor 状态设置为 State::Processing
  • +
  • Event::Async:表示 Processor 需要调用 async_process 进行处理,将 Processor push 到 schedule_queueasync_queue 中,并将 Processor 状态设置为 State::Processing
  • +
+

schedule_queue 的工作过程:

+
    +
  1. 首先初始化两个 VecDeque: need_schedule_nodes: VecDeque<NodeIndex>need_schedule_edges: VecDeque<DirectedEdge> 分别用来存放需要进行 schedule 的 NodeIndex 和 DirectedEdge,然后将 executor_pid push need_schedule_nodes 中。
  2. +
  3. 只要这两个 VecDeque 任意一个不为空,我们就需要不断地进行 schedule。
  4. +
  5. 每次 schedule 时,首先我们会判断 need_schedule_nodes 是否为空,如果它为空,那 need_schedule_edges 一定不为空,此时我们从 need_schedule_edges 中 pop 出一条 DirectedEdge edge,然后获得这条 edge 的 target node(注意这个 target node 不是 edge 的指向,DirectedEdge 有两种类型:SourceTarget,当 Processor 的 input 改变时,会在 triger 的 update_list 中 push 一条 DirectedEdge::Target(self_.index),而如果是 Processor 的 output 改变,则 push 一条 DirectedEdge::Source(self_.index)),如果 target node 的状态为 State::Idle,表示它在上一次调用 event 时返回的 Event 状态为 Event::NeedDataEvent::NeedConsume,即它上次 event 时 input 需要数据或 output 数据需要被消费,而它现在的状态可能是 input 的数据已经来了或者 output 的数据被消费了,因此我们需要将其 push 到 need_schedule_nodes 中来再次调用 event 看看是否可以推动这个 Processor。
  6. +
  7. 然后我们尝试从 need_schedule_nodes pop 出一个 NodeIndex,并从 ExecutingGraph 中得到这个 Node,然后调用它的 Processor 的 event,然后根据返回的 Event 状态来进行下一步工作(如开头描述)。
  8. +
  9. 最后根据将 triger 中的 updated_list 中的 DirectedEdge 都 push 到 need_schedule_edges 中。
  10. +
  11. 如果 need_schedule_nodesneed_schedule_edges 不为空则开始下一次 schedule。
  12. +
  13. schedule 结束,将 schedule_queue 返回。
  14. +
+

(5)调用 schedule_queue.schedule 处理 schedule_queue 中的 tasks

+
// src/query/service/src/pipelines/executor/executor_graph.rs
+impl ScheduleQueue {
+    // ...
+    pub fn schedule(
+        mut self,
+        global: &Arc<ExecutorTasksQueue>,
+        context: &mut ExecutorWorkerContext,
+        executor: &PipelineExecutor,
+    ) {
+        debug_assert!(!context.has_task());
+
+        while let Some(processor) = self.async_queue.pop_front() {
+            Self::schedule_async_task(
+                processor,
+                context.query_id.clone(),
+                executor,
+                context.get_worker_num(),
+                context.get_workers_condvar().clone(),
+                global.clone(),
+            )
+        }
+
+        if !self.sync_queue.is_empty() {
+            self.schedule_sync(global, context);
+        }
+
+        if !self.sync_queue.is_empty() {
+            self.schedule_tail(global, context);
+        }
+    }
+    // ...
+}
+
+
    +
  1. 对于 async_queue 中的 Processor,我们将其 push 到 async_runtime 中,当 Processor 调用 async_process 完成异步任务完成后,会将 CompletedAsyncTask push 到 global_tasks_queue 中。
  2. +
  3. 对于 sync_queue 中的 Processor,我们首先调用 schedule_sync 取出一个 Processor 并把包装为一个 ExecutorTask::Sync(processor) 任务交给当前线程继续执行。然后将剩下的 Processor 都包装为 Processor push 到 global_tasks_queue 中,让其他线程取出 task 并行执行。
  4. +
+

SyncSourcer

+
// src/query/pipeline/source/src/sync_source.rs
+#[async_trait::async_trait]
+impl<T: 'static + SyncSource> Processor for SyncSourcer<T> {
+    fn name(&self) -> String {
+        T::NAME.to_string()
+    }
+
+    fn as_any(&mut self) -> &mut dyn Any {
+        self
+    }
+
+    fn event(&mut self) -> Result<Event> {
+        if self.is_finish {
+            self.output.finish();
+            return Ok(Event::Finished);
+        }
+
+        if self.output.is_finished() {
+            return Ok(Event::Finished);
+        }
+
+        if !self.output.can_push() {
+            return Ok(Event::NeedConsume);
+        }
+
+        match self.generated_data.take() {
+            None => Ok(Event::Sync),
+            Some(data_block) => {
+                self.output.push_data(Ok(data_block));
+                Ok(Event::NeedConsume)
+            }
+        }
+    }
+
+    fn process(&mut self) -> Result<()> {
+        match self.inner.generate()? {
+            None => self.is_finish = true,
+            Some(data_block) => {
+                let progress_values = ProgressValues {
+                    rows: data_block.num_rows(),
+                    bytes: data_block.memory_size(),
+                };
+                self.scan_progress.incr(&progress_values);
+                self.generated_data = Some(data_block)
+            }
+        };
+
+        Ok(())
+    }
+}
+
+
// src/query/storages/fuse/src/operations/read/parquet_data_source_reader.rs
+impl SyncSource for ReadParquetDataSource<true> {
+    const NAME: &'static str = "SyncReadParquetDataSource";
+
+    fn generate(&mut self) -> Result<Option<DataBlock>> {
+        match self.partitions.steal_one(self.id) {
+            None => Ok(None),
+            Some(part) => Ok(Some(DataBlock::empty_with_meta(DataSourceMeta::create(
+                vec![part.clone()],
+                vec![self.block_reader.sync_read_columns_data_by_merge_io(
+                    &ReadSettings::from_ctx(&self.partitions.ctx)?,
+                    part,
+                )?],
+            )))),
+        }
+    }
+}
+
+

process

+

首先调用 inner (例如 ReadParquetDataSource,它实现了 trait SyncSource) 的 generate 获得一个空的 DataBlock,这个 DataBlock 数据为空,但是 meta 不为空,存有 partdata。将这个 data_block 赋值给 self.generated_data

+

event

+

在下一次调用 event 的时候将 self.generated_data 通过 self.output.push_data(Ok(data_block)) 发送出去,并返回 Event::NeedConsume 这个状态。如果 !self.output.can_push() 为 true 的话,说明现在有 data_block 在 output 中,返回 Event::NeedConsume 状态。

+

DeserializeDataTransform

+
// src/query/storages/fuse/src/operations/read/parquet_data_source_deserializer.rs
+#[async_trait::async_trait]
+impl Processor for DeserializeDataTransform {
+    fn name(&self) -> String {
+        String::from("DeserializeDataTransform")
+    }
+
+    fn as_any(&mut self) -> &mut dyn Any {
+        self
+    }
+
+    fn event(&mut self) -> Result<Event> {
+        if self.output.is_finished() {
+            self.input.finish();
+            self.uncompressed_buffer.clear();
+            return Ok(Event::Finished);
+        }
+
+        if !self.output.can_push() {
+            self.input.set_not_need_data();
+            return Ok(Event::NeedConsume);
+        }
+
+        if let Some(data_block) = self.output_data.take() {
+            self.output.push_data(Ok(data_block));
+            return Ok(Event::NeedConsume);
+        }
+
+        if !self.chunks.is_empty() {
+            if !self.input.has_data() {
+                self.input.set_need_data();
+            }
+
+            return Ok(Event::Sync);
+        }
+
+        if self.input.has_data() {
+            let mut data_block = self.input.pull_data().unwrap()?;
+            if let Some(source_meta) = data_block.take_meta() {
+                if let Some(source_meta) = DataSourceMeta::downcast_from(source_meta) {
+                    self.parts = source_meta.part;
+                    self.chunks = source_meta.data;
+                    return Ok(Event::Sync);
+                }
+            }
+
+            unreachable!();
+        }
+
+        if self.input.is_finished() {
+            self.output.finish();
+            self.uncompressed_buffer.clear();
+            return Ok(Event::Finished);
+        }
+
+        self.input.set_need_data();
+        Ok(Event::NeedData)
+    }
+
+    fn process(&mut self) -> Result<()> {
+        let part = self.parts.pop();
+        let chunks = self.chunks.pop();
+        if let Some((part, read_res)) = part.zip(chunks) {
+            let start = Instant::now();
+
+            let columns_chunks = read_res.columns_chunks()?;
+            let part = FusePartInfo::from_part(&part)?;
+
+            let data_block = self.block_reader.deserialize_parquet_chunks_with_buffer(
+                &part.location,
+                part.nums_rows,
+                &part.compression,
+                &part.columns_meta,
+                columns_chunks,
+                Some(self.uncompressed_buffer.clone()),
+            )?;
+
+            // Perf.
+            {
+                metrics_inc_remote_io_deserialize_milliseconds(start.elapsed().as_millis() as u64);
+            }
+
+            let progress_values = ProgressValues {
+                rows: data_block.num_rows(),
+                bytes: data_block.memory_size(),
+            };
+            self.scan_progress.incr(&progress_values);
+
+            self.output_data = Some(data_block);
+        }
+
+        Ok(())
+    }
+}
+
+

event

+

(1)如果 self.output.is_finished() 为 true,则调用 self.input.finish() 并返回 Event::Finished

+

(2)如果 !self.output.can_push() 的话,表示上一次 push 出去的数据还没被消费,对 input 调用 set_not_need_data 表示不需要数据,返回 Event::NeedConsume

+

(3)process 处理好的数据会放到 self.output_data 中,因此如果 +self.output_data.take() 有数据的话,则调用 self.output.push_data(Ok(data_block)) 将它发送出去,并返回 Event::NeedConsume

+

(4)如果 self.input.has_data() 为 true,即 input 有数据,则调用 self.input.pull_data().unwrap()? 将 data_block pull 过来,然后获取其中的 BlockMetaInfo 并将其 downcast 成 DataSourceMeta,然后给 self.partsself.chunks 赋值,返回 Event::Sync 状态。

+

(5)在(4)之前如果 !self.chunks.is_empty() 为 true,这时候我们正在处理之前的 data_block,因此要返回 Event::Sync 这个状态。此外因为这时候我们已经把上一个 data_block pull 过来了,input 可能为空,如果 input 没有数据的话,我们需要将 input set_need_data,为下一次 pull 做准备。

+

(6)如果 self.input.is_finished() 为 ture,则调用 self.output.finish() 并返回 Event::Finished

+

(7)当前 Processor 既没有结束,也没有数据,因此对 input self.input.set_need_data(),返回 Event::NeedData

+

process

+

每次调用 process 会处理一块 parquet_chunks,将其反序列化为数据不为空的 DataBlock,然后将其转交给 self.output_data 等待下一次 event 发送出去。

+

AccumulatingTransformer

+
// src/query/pipeline/transforms/src/processors/transforms/transform_accmulating.rs
+#[async_trait::async_trait]
+impl<T: AccumulatingTransform + 'static> Processor for AccumulatingTransformer<T> {
+    fn name(&self) -> String {
+        String::from(T::NAME)
+    }
+
+    fn as_any(&mut self) -> &mut dyn Any {
+        self
+    }
+
+    fn event(&mut self) -> Result<Event> {
+        if self.output.is_finished() {
+            if !self.called_on_finish {
+                return Ok(Event::Sync);
+            }
+
+            self.input.finish();
+            return Ok(Event::Finished);
+        }
+
+        if !self.output.can_push() {
+            self.input.set_not_need_data();
+            return Ok(Event::NeedConsume);
+        }
+
+        if let Some(data_block) = self.output_data.pop_front() {
+            self.output.push_data(Ok(data_block));
+            return Ok(Event::NeedConsume);
+        }
+
+        if self.input_data.is_some() {
+            return Ok(Event::Sync);
+        }
+
+        if self.input.has_data() {
+            self.input_data = Some(self.input.pull_data().unwrap()?);
+            return Ok(Event::Sync);
+        }
+
+        if self.input.is_finished() {
+            return match !self.called_on_finish {
+                true => Ok(Event::Sync),
+                false => {
+                    self.output.finish();
+                    Ok(Event::Finished)
+                }
+            };
+        }
+
+        self.input.set_need_data();
+        Ok(Event::NeedData)
+    }
+
+    fn process(&mut self) -> Result<()> {
+        if let Some(data_block) = self.input_data.take() {
+            self.output_data.extend(self.inner.transform(data_block)?);
+            return Ok(());
+        }
+
+        if !self.called_on_finish {
+            self.called_on_finish = true;
+            self.output_data.extend(self.inner.on_finish(true)?);
+        }
+
+        Ok(())
+    }
+}
+
+

event

+

整体上与 DeserializeDataTransformevent 类似,不同的地方在于:

+

(1)self.output_data 的类型为 VecDeque<DataBlock>,而不是 DataBlock,可以发送数据时,从调用 self.output_data.pop_front() 从队头取出一个 DataBlock 并 push 出去。

+

(2)在 self.output.is_finished()self.input.is_finished() 为 true 时,首先判断 called_on_finish 是否为 true,如果不为 true 的话,表示还没有调用 inner 的 on_finish,这时候返回 Event::Sync 而不是 Event::Finished

+

process

+

(1)如果 input_data 中有数据,则获取 input_data 中的 DataBlock 并用它调用 inner (例如 TransformPartialGroupBy,它实现了 trait AccumulatingTransform)的 transform(data_block)? 来获取需要 spill 的 data_blocks,这些 data_block 的 columns 是空的,但是 meta 不为空,meta 的类型为 AggregateMeta::Spilling;如果当前的 hash table 不大,则返回的结果是 vec![]transform 的分析在下面。

+

(2)如果 input_data 中没有数据且 called_on_finish 为 false,则调用 inner 的 on_finish 来获取 DataBlock,同样,这些 DataBlock 的 columns 是空的,但是 meta 不为空,meta 的类型为 AggregateMeta::HashTableon_finish 的分析在下面。

+

transform

+
// src/query/service/src/pipelines/processors/transforms/aggregator/transform_group_by_partial.rs
+impl<Method: HashMethodBounds> AccumulatingTransform for TransformPartialGroupBy<Method> {
+    const NAME: &'static str = "TransformPartialGroupBy";
+
+    fn transform(&mut self, block: DataBlock) -> Result<Vec<DataBlock>> {
+        let block = block.convert_to_full();
+        let group_columns = self
+            .group_columns
+            .iter()
+            .map(|&index| block.get_by_offset(index))
+            .collect::<Vec<_>>();
+
+        let group_columns = group_columns
+            .iter()
+            .map(|c| (c.value.as_column().unwrap().clone(), c.data_type.clone()))
+            .collect::<Vec<_>>();
+
+        unsafe {
+            let rows_num = block.num_rows();
+            let state = self.method.build_keys_state(&group_columns, rows_num)?;
+
+            match &mut self.hash_table {
+                HashTable::MovedOut => unreachable!(),
+                HashTable::HashTable(cell) => {
+                    for key in self.method.build_keys_iter(&state)? {
+                        let _ = cell.hashtable.insert_and_entry(key);
+                    }
+                }
+                HashTable::PartitionedHashTable(cell) => {
+                    for key in self.method.build_keys_iter(&state)? {
+                        let _ = cell.hashtable.insert_and_entry(key);
+                    }
+                }
+            };
+
+            #[allow(clippy::collapsible_if)]
+            if Method::SUPPORT_PARTITIONED {
+                if matches!(&self.hash_table, HashTable::HashTable(cell)
+                    if cell.len() >= self.settings.convert_threshold ||
+                        cell.allocated_bytes() >= self.settings.spilling_bytes_threshold_per_proc
+                ) {
+                    if let HashTable::HashTable(cell) = std::mem::take(&mut self.hash_table) {
+                        self.hash_table = HashTable::PartitionedHashTable(
+                            PartitionedHashMethod::convert_hashtable(&self.method, cell)?,
+                        );
+                    }
+                }
+
+                if matches!(&self.hash_table, HashTable::PartitionedHashTable(cell) if cell.allocated_bytes() > self.settings.spilling_bytes_threshold_per_proc)
+                {
+                    if let HashTable::PartitionedHashTable(v) = std::mem::take(&mut self.hash_table)
+                    {
+                        let _dropper = v._dropper.clone();
+                        let cells = PartitionedHashTableDropper::split_cell(v);
+                        let mut blocks = Vec::with_capacity(cells.len());
+                        for (bucket, cell) in cells.into_iter().enumerate() {
+                            if cell.hashtable.len() != 0 {
+                                blocks.push(DataBlock::empty_with_meta(
+                                    AggregateMeta::<Method, ()>::create_spilling(
+                                        bucket as isize,
+                                        cell,
+                                    ),
+                                ));
+                            }
+                        }
+
+                        let method = PartitionedHashMethod::<Method>::create(self.method.clone());
+                        let new_hashtable = method.create_hash_table()?;
+                        self.hash_table = HashTable::PartitionedHashTable(HashTableCell::create(
+                            new_hashtable,
+                            _dropper.unwrap(),
+                        ));
+                        return Ok(blocks);
+                    }
+
+                    unreachable!()
+                }
+            }
+        }
+
+        Ok(vec![])
+    }
+}
+
+

(1)首先调用 block.convert_to_full() 将 DataBlock 填充满:对于每个 BlockEntry,如果是 Value::Scalar 类型,则将其重复 self.num_rows 次转变为 Value::Column,如果原本就是 Value::Column 类型的话就简单 clone 一下。

+

(2)从 datablock 中获取用于 group by 的列 group_columns: Vec<&BlockEntry>,然后再转变为 Vec<(Column, DataType)>

+

(3)调用 self.method.build_keys_state(&group_columns, rows_num)group_columns group_columns 变为 KeyState:变为 unsigned 类型,

+

(4)调用 build_keys_iter 来获取 group by key 的 iter,并将每个 key 插入到 hash table 中。

+

(5)如果 hash table 的长度大于 convert_threshold 或者分配的字节数大于 spilling_bytes_threshold_per_proc,则将其装换为 PartitionedHashTable

+

(6)如果一个 PartitionedHashTable 的长度大于 convert_threshold 或者分配的字节数大于 spilling_bytes_threshold_per_proc,这时候需要 spill 到存储上:将当前 hash table 转变为 blocks: Vec<DataBlock>,这些 DataBlock 的 columns 为空,meta 不为空,类型为:AggregateMeta::Spilling,然后创建一个新的 hash table,并将 blocks 返回。

+

(7)如果当前 hash table 不是很大,则返回 vec![]

+

build_keys_state

+

src/query/expression/src/kernels/group_by_hash.rs

+

(1)如果 group_by 只有一个字段的且这个字段是整数类型的话,则将这一列 cast 为 unsigned 类型,包装在 KeysState 中返回。

+

(2)否则调用 build_keys_vec 来构建 key,并将 key cast 成整数类型包装在 KeysState 中返回。

+

on_finish

+
// src/query/service/src/pipelines/processors/transforms/aggregator/transform_group_by_partial.rs
+impl<Method: HashMethodBounds> AccumulatingTransform for TransformPartialGroupBy<Method> {
+  	// ...
+    fn on_finish(&mut self, _output: bool) -> Result<Vec<DataBlock>> {
+        Ok(match std::mem::take(&mut self.hash_table) {
+            HashTable::MovedOut => unreachable!(),
+            HashTable::HashTable(cell) => match cell.hashtable.len() == 0 {
+                true => vec![],
+                false => vec![DataBlock::empty_with_meta(
+                    AggregateMeta::<Method, ()>::create_hashtable(-1, cell),
+                )],
+            },
+            HashTable::PartitionedHashTable(v) => {
+                let cells = PartitionedHashTableDropper::split_cell(v);
+                let mut blocks = Vec::with_capacity(cells.len());
+                for (bucket, cell) in cells.into_iter().enumerate() {
+                    if cell.hashtable.len() != 0 {
+                        blocks.push(DataBlock::empty_with_meta(
+                            AggregateMeta::<Method, ()>::create_hashtable(bucket as isize, cell),
+                        ));
+                    }
+                }
+
+                blocks
+            }
+        })
+    }
+}
+
+

HashTable 或者 PartitionedHashTable 转变为 DataBlock 返回,这些 DataBlock 的 columns 字段为空,meta 字段类型为 AggregateMeta::HashTable

+

如果 hash table 是 HashTable::HashTable 类型,则返回的 bucket id 为 -1,如果是 HashTable::PartitionedHashTable,则先调用 split_cell 将其 split 成 cells,然后再返回,bucket id 为 0 ~ cells.len() - 1。

+

TransformGroupBySpillWriter

+
// src/query/service/src/pipelines/processors/transforms/aggregator/serde/transform_group_by_spill_writer.rs
+#[async_trait::async_trait]
+impl<Method: HashMethodBounds> Processor for TransformGroupBySpillWriter<Method> {
+    fn name(&self) -> String {
+        String::from("TransformGroupBySpillWriter")
+    }
+
+    fn as_any(&mut self) -> &mut dyn Any {
+        self
+    }
+
+    fn event(&mut self) -> Result<Event> {
+        if self.output.is_finished() {
+            self.input.finish();
+            return Ok(Event::Finished);
+        }
+
+        if !self.output.can_push() {
+            self.input.set_not_need_data();
+            return Ok(Event::NeedConsume);
+        }
+
+        if let Some(spilled_meta) = self.spilled_meta.take() {
+            self.output
+                .push_data(Ok(DataBlock::empty_with_meta(spilled_meta)));
+            return Ok(Event::NeedConsume);
+        }
+
+        if self.writing_data_block.is_some() {
+            self.input.set_not_need_data();
+            return Ok(Event::Async);
+        }
+
+        if self.spilling_meta.is_some() {
+            self.input.set_not_need_data();
+            return Ok(Event::Sync);
+        }
+
+        if self.input.has_data() {
+            let mut data_block = self.input.pull_data().unwrap()?;
+
+            if let Some(block_meta) = data_block
+                .get_meta()
+                .and_then(AggregateMeta::<Method, ()>::downcast_ref_from)
+            {
+                if matches!(block_meta, AggregateMeta::Spilling(_)) {
+                    self.input.set_not_need_data();
+                    let block_meta = data_block.take_meta().unwrap();
+                    self.spilling_meta = AggregateMeta::<Method, ()>::downcast_from(block_meta);
+                    return Ok(Event::Sync);
+                }
+            }
+
+            self.output.push_data(Ok(data_block));
+            return Ok(Event::NeedConsume);
+        }
+
+        if self.input.is_finished() {
+            self.output.finish();
+            return Ok(Event::Finished);
+        }
+
+        self.input.set_need_data();
+        Ok(Event::NeedData)
+    }
+
+    fn process(&mut self) -> Result<()> {
+        if let Some(spilling_meta) = self.spilling_meta.take() {
+            if let AggregateMeta::Spilling(payload) = spilling_meta {
+                let bucket = payload.bucket;
+                let data_block = serialize_group_by(&self.method, payload)?;
+                let columns = get_columns(data_block);
+
+                let mut total_size = 0;
+                let mut columns_data = Vec::with_capacity(columns.len());
+                for column in columns.into_iter() {
+                    let column = column.value.as_column().unwrap();
+                    let column_data = serialize_column(column);
+                    total_size += column_data.len();
+                    columns_data.push(column_data);
+                }
+
+                self.writing_data_block = Some((bucket, total_size, columns_data));
+                return Ok(());
+            }
+
+            return Err(ErrorCode::Internal(""));
+        }
+
+        Ok(())
+    }
+
+    async fn async_process(&mut self) -> Result<()> {
+        if let Some((bucket, total_size, data)) = self.writing_data_block.take() {
+            let instant = Instant::now();
+            let unique_name = GlobalUniqName::unique();
+            let location = format!("{}/{}", self.location_prefix, unique_name);
+            let object = self.operator.object(&location);
+
+            // temp code: waiting https://github.com/datafuselabs/opendal/pull/1431
+            let mut write_data = Vec::with_capacity(total_size);
+            let mut columns_layout = Vec::with_capacity(data.len());
+
+            for data in data.into_iter() {
+                columns_layout.push(data.len());
+                write_data.extend(data);
+            }
+
+            object.write(write_data).await?;
+            info!(
+                "Write aggregate spill {} successfully, elapsed: {:?}",
+                &location,
+                instant.elapsed()
+            );
+
+            self.spilled_meta = Some(AggregateMeta::<Method, ()>::create_spilled(
+                bucket,
+                location,
+                columns_layout,
+            ));
+        }
+
+        Ok(())
+    }
+}
+
+fn get_columns(data_block: DataBlock) -> Vec<BlockEntry> {
+    data_block.columns().to_vec()
+}
+
+

event

+

与前面几个 event 类似,不同的地方在于:

+

(1)当 self.input.has_data() 为 true 时,我们将从 DataBlock 中取出 meta,然后 downcast 成 AggregateMeta,检查其类型:(1)如果发现类型是 AggregateMeta::Spilling,则我们需要将其 spill 到存储上,于是我们将 downcast 后的结果赋值给 self.spilling_meta,等待在 process 中处理,返回 Event::Sync;(2)其他类型则直接调用 self.output.push_data(Ok(data_block)) push 出去,然后返回 Event::NeedConsume

+

(2)如果发现 self.spilled_meta 有数据,表示这个数据已经被 spill 了,则将这个 meta 包装成一个空的 DataBlock 并 push 出去,返回 Event::NeedConsume

+

process

+

process 是对 self.spilling_meta 进行处理,将其转变为 self.writing_data_block,随后交给 async_process spill 到存储上:

+

(1)首先检查 self.spilling_meta 中是否有数据,并获得 spilling_meta 中的 hash table。

+

(2)将 hash table 序列化为 DataBlock,并取出其中的列 columns: Vec<BlockEntry>,然后将每一列序列化为字节 column_data 并 push 到 columns_data 中。

+

(3)最后对 self.writing_data_block 进行赋值:self.writing_data_block = Some((bucket, total_size, columns_data));,等待在 async_process 中被 spill 到存储中。

+

async_process

+

self.writing_data_block spill 到存储中,然后将 spilled 后数据的 bucketlocationcolumns_layout 信息包装成一个 AggregateMeta::Spilled 类型的 meta 赋值给 self.spilled_meta,等待下一次调用 event 发送出去。

+

TransformPartitionBucket

+

src/query/service/src/pipelines/processors/transforms/aggregator/transform_partition_bucket.rs

+

首先介绍一下 TransformPartitionBucket,它的 input 可以有多个,但是 output 只有一个,它的作用是将多个 bucket id 相同的 DataBlock 组成一个 AggregateMeta::Partitioned 发送出去。

+

initialize_all_inputs

+
impl<Method: HashMethodBounds, V: Copy + Send + Sync + 'static>
+    TransformPartitionBucket<Method, V>
+{
+  	// ...
+    fn initialize_all_inputs(&mut self) -> Result<bool> {
+        self.initialized_all_inputs = true;
+
+        for index in 0..self.inputs.len() {
+            if self.inputs[index].port.is_finished() {
+                continue;
+            }
+
+            // We pull the first unsplitted data block
+            if self.inputs[index].bucket > SINGLE_LEVEL_BUCKET_NUM {
+                continue;
+            }
+
+            if !self.inputs[index].port.has_data() {
+                self.inputs[index].port.set_need_data();
+                self.initialized_all_inputs = false;
+                continue;
+            }
+
+            let data_block = self.inputs[index].port.pull_data().unwrap()?;
+            self.inputs[index].bucket = self.add_bucket(data_block);
+
+            if self.inputs[index].bucket <= SINGLE_LEVEL_BUCKET_NUM {
+                self.inputs[index].port.set_need_data();
+                self.initialized_all_inputs = false;
+            }
+        }
+
+        Ok(self.initialized_all_inputs)
+    }
+  	// ...
+}
+
+

首先我们先看一下 initialize_all_inputs 这个函数,每次调用 event 的时候,我们都会首先:

+
// We pull the first unsplitted data block
+if !self.initialized_all_inputs && !self.initialize_all_inputs()? {
+    return Ok(Event::NeedData);
+}
+
+

它的作用是将 unsplitted data block,即 bucket id 为 -1 的 block 全 pull 过来,我们先回顾一下 TransformPartitionBucket 的上游的上游,即 AccumulatingTransformer,在 AccumulatingTransformer 中,我们如果 hash table 过大,我们会将其 spill 到存储上,而如果没有 spill 的话,会在 on_finish 的时候返回 bucket id 为 -1 的 DataBlock,而一旦有 spill,则不会有 bucket id 为 -1 的 DataBlock 被 push 到下游,上面这段代码利用了这一特点保证了 bucket id 为 -1 的 DataBlock 全都 pull 过来后,才会向下,执行,否则会一直返回 Event::NeedData

+

event

+

(1)如果 self.output.is_finished() 为 true,调用每个 input 的 finish 并清空 buckets_blocks

+

(2)利用 !self.buckets_blocks.is_empty() && !self.unsplitted_blocks.is_empty() 将所有的 unsplitted data block 全都 pull 过来后才会向下执行。

+

(3)如果 !self.buckets_blocks.is_empty() && !self.unsplitted_blocks.is_empty() 为 true,表示在 pull unsplitted data 的时候把 bucket id 不为 -1 的也 pull 过来了,这时候返回 Event::Sync,进而在下次调用 process 的时候将 bucket id 为 -1 的 DataBlock partition 为多个 bucket id 不为 -1 的 DataBlock。

+

(4)如果 !self.buckets_blocks.is_empty() && !self.unsplitted_blocks.is_empty() 为 false,表示 pull 过来的都是 bucket id 为 -1 的 DataBlock 或者 bucket id 为 -1 的 DataBlock 已经被 partition 为 bucket id 不为 -1 的 DataBlock 了。这时候我们首先调用 try_push_data_block 来 push bucket id 为 -1 的 DataBlock,bucket id 不为 -1 由于代码中 self.pushing_bucket < self.working_bucket 的限制还不能被 push。

+

(5)然后就是一个 loop 循环,具体做的事情就是 bucket id 从 0 开始,等 bucket id 为 0 的都 pull 过来了,再 pull bucket id 为 1 的,以此类推,一旦某个所有的 input 都 finish 了或者某个 input 的数据没准备好,则 break;

+

(6)如果之前那次 push 有数据被 push 了或本次 push 返回 true,则返回 Event::NeedConsume

+

(7)从 buckets_blocks 中 pop first,调用 convert_blocks 将多个 bucket id 相同的 DataBlock 组成一个 AggregateMeta::Partitioned 发送出去。(在 try_push_two_level 中, self.pushing_bucket 是递增不会退的,因此可能 bucket id 小的 DataBlock 不会在 try_push_two_level 中被 push 出去,而会在这里被 push 出去。

+

add_bucket

+
impl<Method: HashMethodBounds, V: Copy + Send + Sync + 'static>
+    TransformPartitionBucket<Method, V>
+{
+  	// ...
+    fn add_bucket(&mut self, data_block: DataBlock) -> isize {
+        if let Some(block_meta) = data_block.get_meta() {
+            if let Some(block_meta) = AggregateMeta::<Method, V>::downcast_ref_from(block_meta) {
+                let (bucket, res) = match block_meta {
+                    AggregateMeta::Spilling(_) => unreachable!(),
+                    AggregateMeta::Partitioned { .. } => unreachable!(),
+                    AggregateMeta::Spilled(payload) => (payload.bucket, SINGLE_LEVEL_BUCKET_NUM),
+                    AggregateMeta::Serialized(payload) => (payload.bucket, payload.bucket),
+                    AggregateMeta::HashTable(payload) => (payload.bucket, payload.bucket),
+                };
+
+                if bucket > SINGLE_LEVEL_BUCKET_NUM {
+                    match self.buckets_blocks.entry(bucket) {
+                        Entry::Vacant(v) => {
+                            v.insert(vec![data_block]);
+                        }
+                        Entry::Occupied(mut v) => {
+                            v.get_mut().push(data_block);
+                        }
+                    };
+
+                    return res;
+                }
+            }
+        }
+
+        self.unsplitted_blocks.push(data_block);
+        SINGLE_LEVEL_BUCKET_NUM
+    }
+  	// ...
+}
+
+

将一个 DataBlock 加到 unsplitted_blocks 或者 buckets_blocks 中,可以看到,bucket id 为 -1 的 DataBlock 都会被 push 到 unsplitted_blocks 中。

+

Process

+
impl<Method: HashMethodBounds, V: Copy + Send + Sync + 'static>
+    TransformPartitionBucket<Method, V>
+{
+    fn process(&mut self) -> Result<()> {
+        let block_meta = self
+            .unsplitted_blocks
+            .pop()
+            .and_then(|mut block| block.take_meta())
+            .and_then(AggregateMeta::<Method, V>::downcast_from);
+
+        match block_meta {
+            None => Err(ErrorCode::Internal(
+                "Internal error, TransformPartitionBucket only recv AggregateMeta.",
+            )),
+            Some(agg_block_meta) => {
+                let data_blocks = match agg_block_meta {
+                    AggregateMeta::Spilled(_) => unreachable!(),
+                    AggregateMeta::Spilling(_) => unreachable!(),
+                    AggregateMeta::Partitioned { .. } => unreachable!(),
+                    AggregateMeta::Serialized(payload) => self.partition_block(payload)?,
+                    AggregateMeta::HashTable(payload) => self.partition_hashtable(payload)?,
+                };
+
+                for (bucket, block) in data_blocks.into_iter().enumerate() {
+                    if let Some(data_block) = block {
+                        match self.buckets_blocks.entry(bucket as isize) {
+                            Entry::Vacant(v) => {
+                                v.insert(vec![data_block]);
+                            }
+                            Entry::Occupied(mut v) => {
+                                v.get_mut().push(data_block);
+                            }
+                        };
+                    }
+                }
+
+                Ok(())
+            }
+        }
+    }
+}
+
+

可以看到 process 是对 bucket id 为 -1 的 DataBlock 调用 partition_blockpartition_hashtable 进行 partition 从而得到 data_blocks,然后将 data_blocks 插入到 buckets_blocks 中。

+

TransformSpillReader

+

src/query/service/src/pipelines/processors/transforms/aggregator/serde/transform_spill_reader.rs

+

如果 DataBlock 不是 Spilled 类型,则直接 push 到下游,否则需要进行一些列处理:

+

TransformSpillReader 的处理是围绕着三个成员变量展开的:reading_metadeserializing_metadeserialized_meta

+
    +
  • reading_meta:上游传来的 AggregateMeta::Spilled 类型的 DataBlock,将它转交给 self.reading_meta 然后返回 Event::Async,在后面会调用 async_process 对其进行异步读取。
  • +
  • deserializing_meta:异步线程会调用 async_processreading_meta 进行处理:按照 reading_meta 中的信息读取存储,并将读到的内容存到的 self.deserializing_meta 中。在后续调用 event 时如果发现 self.deserializing_meta.is_some() 为 true,则返回 Event::Sync 来让线程调用 process 进行反序列化。
  • +
  • deserialized_meta:将 deserializing_meta 中的数据进行反序列化,对于 AggregateMeta::Spilled 类型的 meta,我们将其分序列化为 AggregateMeta::Serialized。而对于 AggregateMeta::Partitioned 类型的 meta,我们将其中每个 meta 都反序列化为 AggregateMeta::Serialized,然后组成一个 AggregateMeta::Partitioned。最终我们将反序列化后的结果转交给 deserialized_meta,让它在下次 event 时被 push 出去。
  • +
+

BlockMetaTransformer

+
// src/query/pipeline/transforms/src/processors/transforms/transform.rs
+#[async_trait::async_trait]
+impl<B: BlockMetaInfo, T: BlockMetaTransform<B>> Processor for BlockMetaTransformer<B, T> {
+    fn name(&self) -> String {
+        String::from(T::NAME)
+    }
+
+    fn as_any(&mut self) -> &mut dyn Any {
+        self
+    }
+
+    fn event(&mut self) -> Result<Event> {
+        if !self.called_on_start {
+            return Ok(Event::Sync);
+        }
+
+        match self.output.is_finished() {
+            true => self.finish_input(),
+            false if !self.output.can_push() => self.not_need_data(),
+            false => match self.output_data.take() {
+                None if self.input_data.is_some() => Ok(Event::Sync),
+                None => self.pull_data(),
+                Some(data) => {
+                    self.output.push_data(Ok(data));
+                    Ok(Event::NeedConsume)
+                }
+            },
+        }
+    }
+
+    fn process(&mut self) -> Result<()> {
+        if !self.called_on_start {
+            self.called_on_start = true;
+            self.transform.on_start()?;
+            return Ok(());
+        }
+
+        if let Some(mut data_block) = self.input_data.take() {
+            debug_assert!(data_block.is_empty());
+            if let Some(block_meta) = data_block.take_meta() {
+                if let Some(block_meta) = B::downcast_from(block_meta) {
+                    let data_block = self.transform.transform(block_meta)?;
+                    self.output_data = Some(data_block);
+                }
+            }
+
+            return Ok(());
+        }
+
+        if !self.called_on_finish {
+            self.called_on_finish = true;
+            self.transform.on_finish()?;
+        }
+
+        Ok(())
+    }
+}
+
+

process

+

如果 input_data 有数据的话,将 block_meta downcast 成实现 trait BlockMetaInfo 的某种 meta,例如 AggregateMeta,然后调用 self.transform.transform(block_meta)? 将 meta 转换 column 不为空的 DataBlock,然后将其转交给 self.output_data 等待下一次 event 时被 push 出去。

+

CompoundBlockOperator

+

调用链:Processor 会包装一个 TransformerTransformer 里面有一个 transform 成员,这个成员就是 BlockOperator 类型,调用 Processorprocess 会调用 Transformerself.transform.transform 进而调用 BlockOperatorexecute 函数将 DataBlock transform 成另外的格式(例如 projection)

+

BlockOperator 有四种类型:

+
// src/query/sql/src/evaluator/block_operator.rs
+/// `BlockOperator` takes a `DataBlock` as input and produces a `DataBlock` as output.
+#[derive(Clone)]
+pub enum BlockOperator {
+    /// Batch mode of map which merges map operators into one.
+    Map { exprs: Vec<Expr> },
+
+    /// Filter the input `DataBlock` with the predicate `eval`.
+    Filter { expr: Expr },
+
+    /// Reorganize the input `DataBlock` with `projection`.
+    Project { projection: Vec<FieldIndex> },
+
+    /// Unnest certain fields of the input `DataBlock`.
+    Unnest { fields: Vec<usize> },
+}
+
+

execute 函数如下:

+
impl BlockOperator {
+    pub fn execute(&self, func_ctx: &FunctionContext, mut input: DataBlock) -> Result<DataBlock> {
+        match self {
+            BlockOperator::Map { exprs } => {
+                for expr in exprs {
+                    let evaluator = Evaluator::new(&input, *func_ctx, &BUILTIN_FUNCTIONS);
+                    let result = evaluator.run(expr)?;
+                    let col = BlockEntry {
+                        data_type: expr.data_type().clone(),
+                        value: result,
+                    };
+                    input.add_column(col);
+                }
+                Ok(input)
+            }
+
+            BlockOperator::Filter { expr } => {
+                assert_eq!(expr.data_type(), &DataType::Boolean);
+
+                let evaluator = Evaluator::new(&input, *func_ctx, &BUILTIN_FUNCTIONS);
+                let filter = evaluator.run(expr)?.try_downcast::<BooleanType>().unwrap();
+                input.filter_boolean_value(&filter)
+            }
+
+            BlockOperator::Project { projection } => {
+                let mut result = DataBlock::new(vec![], input.num_rows());
+                for index in projection {
+                    result.add_column(input.get_by_offset(*index).clone());
+                }
+                Ok(result)
+            }
+
+            BlockOperator::Unnest { fields } => {
+                let num_rows = input.num_rows();
+                let mut unnest_columns = Vec::with_capacity(fields.len());
+                for field in fields {
+                    let col = input.get_by_offset(*field);
+                    let array_col = match &col.value {
+                        Value::Scalar(Scalar::Array(col)) => {
+                            Box::new(ArrayColumnBuilder::<AnyType>::repeat(col, num_rows).build())
+                        }
+                        Value::Column(Column::Array(col)) => col.clone(),
+                        _ => {
+                            return Err(ErrorCode::Internal(
+                                "Unnest can only be applied to array types.",
+                            ));
+                        }
+                    };
+                    unnest_columns.push((*field, array_col));
+                }
+                Self::fit_unnest(input, unnest_columns)
+            }
+        }
+    }
+}
+
+

至此,一条 SQL 的 pipeline 就执行完毕了。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 多云转晴:Databend 的天空计算之路 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 第一弹 - minibend 简介 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/source-reading/pipeline-model-graph-based/index.html b/docs/source-reading/pipeline-model-graph-based/index.html new file mode 100644 index 0000000..c58d721 --- /dev/null +++ b/docs/source-reading/pipeline-model-graph-based/index.html @@ -0,0 +1,706 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 源码阅读: 图解 pipeline 调度模型 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 源码阅读: 图解 pipeline 调度模型

+ +

作者:JackTan25 | Databend Contributor

+

一.基于图的初始化

+

+

上图便是 databend 的一条 pipeline 结构,通常对于每一个 PipeItem,这里只会有一个 input_port 和 output_port,一个 Pipe 当中的 PipeItem 的数量则通常代表着并行度.每一个 PipeItem 里面对应着一个算子(不过在有些情况下并不一定一个 pipeItem 就只有一对 input_port 和 output_port,所以上图画的更加广泛一些),算子的推进由调度模型来触发

+

将 pipeline 初始化为 graph:这里细致展示下生成的过程

+

databend 采取的是采取的是 StableGraph 这个结构,我们最开始是得到了下面第一张图这样的 Pipeline,我们最后生成的是下面第二张图的 graph.

+

+

+

上面第二张图的的连接只是一个物理上的单纯图的连接,但是 node 内部 pipe_item 对应的 port 没有对接起来.我们还需要关心的是具体如何把对应的 port 给连接起来的.在构建图的时候每一个 PipeItem 包装为一个 node,包装的过程是以 Pipe 为顺序的.这样我们就为上面每一个 PipeItem 都加上了一个 Node 编号,后面我们需要按照为对应的 input_port 和 output_port 去加上 edge,我们的连接是一个平行的连接.

+

我们将构建过程当中需要使用到的结构做一个介绍:

+
// 一个Node对应一个PipeItem
+struct Node {
+    // node的状态记录,其实应该理解为是记录
+    // pipeline执行过程当中一个最小执行
+    // 单元PipeItem的状态,一共有如下三种状态:
+    // Idle,Processing,Finished,
+    state: std::sync::Mutex<State>,
+
+    updated_list: Arc<UpdateList>,
+    // 一下是pipeItem的内容
+    inputs_port: Vec<Arc<InputPort>>,
+    outputs_port: Vec<Arc<OutputPort>>,
+    processor: ProcessorPtr,
+}
+
+pub struct UpdateList {
+    inner: UnsafeCell<UpdateListMutable>,
+}
+pub struct UpdateListMutable {
+    // update_input与update_output调用时更新,用于
+    // 调度模型的任务调度
+    updated_edges: Vec<DirectedEdge>,
+    // 对于Node而言,其上的每一个input_port和output_port都会对应
+    // 一个trigger,我们从edge0,edge1,...,edgen(编号就是上图示例)
+    // 这样每次给source_node为其对应的output_port添加一个trigger
+    // 为target_node的input_port添加一个trigger
+    updated_triggers: Vec<Arc<UnsafeCell<UpdateTrigger>>>,
+}
+
+// 用于判断调度前驱node还是后驱node
+pub enum DirectedEdge {
+    Source(EdgeIndex),
+    Target(EdgeIndex),
+}
+
+// trigger的作用就是用来后面调度模型推进pipeline向下
+// 执行调度任务使用的
+pub struct UpdateTrigger {
+ // 记录该trigger对应的是哪一个边
+ index: EdgeIndex,
+ // 记录其属于哪一个UpdateListMutable
+ update_list: *mut UpdateListMutable,
+ // 初始化为0
+ version: usize,
+ // 初始化为0
+ prev_version: usize,
+}
+// 上面的例子最后我们得到的graph初始化后应该是下面这样
+
+

+
// 而对于input_port和output_port的数据的传递,则是两者之间共享一个SharedData
+pub struct SharedStatus {
+    // SharedData按照8字节对齐,所以其地址
+    // 最后三位永远为0,在这里我们会利用这三
+    // 位来标记当前port的状态,一共有三种
+    // NEED_DATA,HAS_DATA,IS_FINISHED
+    data: AtomicPtr<SharedData>,
+}
+
+

二.基于 work-steal 与状态机的并发调度模型

+

初始化调度是将我们的 graph 的所有出度为 0 的 Node 作为第一次任务调度节点,对应我们的例子就是 Node4,Node5 每一次调度都是抽取出 graph 当中的同步任务和异步任务,下图是 pipeline 的调度模型,用于抽取出当前 graph 当中可执行的同步 processor 和异步 processor,调度模型的输入是最上面的 graph,而输出则是 sync_processor_queue 和 async_processor_queue,无论是在初始化时还是在后面继续执行的过程都是利用的下面的调度模型来进行调度.调度模型的执行终点是 need_schedule_nodes 和 need_schedule_edges 均为空

+

+

三.执行模型

+

执行模型对应相关结构如下:

+
struct ExecutorTasks {
+    // 记录当前还剩余的task的数量
+    tasks_size: usize,
+    workers_waiting_status: WorkersWaitingStatus,
+    // 记录同步任务,其大小等于系统当前允许的thread数量
+    workers_sync_tasks: Vec<VecDeque<ProcessorPtr>>,
+    // 记录已完成的异步任务,其大小等于系统当前允许的thread数量
+    workers_completed_async_tasks: Vec<VecDeque<CompletedAsyncTask>>,
+}
+
+// 用于记录等待线程和活跃线程
+pub struct WorkersWaitingStatus {
+    stack: Vec<usize>,
+    stack_size: usize,
+    worker_pos_in_stack: Vec<usize>,
+}
+
+pub struct WorkersCondvar {
+    // 记录还未执行完的异步任务
+    waiting_async_task: AtomicUsize,
+    // 用于唤醒机制
+    workers_condvar: Vec<WorkerCondvar>,
+}
+
+pub struct ProcessorAsyncTask {
+    // worker_id代表的是当前异步任务对应的线程id
+    worker_id: usize,
+    // 在graph当中的节点位置
+    processor_id: NodeIndex,
+    // 全局的任务队列
+    queue: Arc<ExecutorTasksQueue>,
+    // 用于work-steal的调度唤醒策略
+    workers_condvar: Arc<WorkersCondvar>,
+    // 一个包装future,见下图的具体包装
+    inner: BoxFuture<'static, Result<()>>,
+}
+
+pub struct ExecutorTasksQueue {
+    // 记录当前执行任务队列是否完成
+    finished: Arc<AtomicBool>,
+    // 通知异步任务结束
+    finished_notify: Arc<Notify>,
+    workers_tasks: Mutex<ExecutorTasks>,
+}
+
+// 唤醒机制
+struct WorkerCondvar {
+    mutex: Mutex<bool>,
+    condvar: Condvar,
+}
+
+

执行模型的流程图如下: +

+

四. 限时机制

+

限时机制其实是比较简单的,其主要的作用就是限制 sql 的 pipeline 执行的时间在规定时间内完成, +如果超时则自动终止.这个机制底层实现就是用了一个异步任务来跟踪,一旦超时就通知整个执行模型结束,这里对应的就是执行模型流程图里面的 finish。

+

以上便是 databend 的机遇状态机和 work-steal 机制的并发调度模型实现.

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 多云转晴:Databend 的天空计算之路 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 第一弹 - minibend 简介 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/source-reading/storage-basics-read-partitions/index.html b/docs/source-reading/storage-basics-read-partitions/index.html new file mode 100644 index 0000000..06a2b7d --- /dev/null +++ b/docs/source-reading/storage-basics-read-partitions/index.html @@ -0,0 +1,685 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 源码阅读: Storage 概要和 Read Partitions | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

Databend 源码阅读: Storage 概要和 Read Partitions

+ +

作者:zhyass | Databend Labs 成员,数据库研发工程师

+

引言

+

Databend 将存储引擎抽象成一个名为 Table 的接口,源码位于 query/catalog/src/table.rs

+

Table 接口定义了 readappendalteroptimizetruncate 以及 recluster 等方法,负责数据的读写和变更。解释器(interpreter)通过调用 Table trait 的方法生成物理执行的 pipeline

+

通过实现 Table 接口的方法,可以定义 Databend 的存储引擎,不同的实现对应不同的引擎。

+

Storage 主要关注 Table 接口的具体实现,涉及表的元信息,索引信息的管理,以及与底层 IO 的交互。

+

目录

+ + + + + + + + + + + + + + + + + + + +
包名作用
common/cache定义与管理缓存,包括磁盘缓存和内存缓存。类型包含表 meta 缓存、查询结果缓存、表数据缓存等。
common/index定义与使用索引,目前支持 bloom filter index、page index、range index。
common/locks管理与使用锁,支持表级别的锁。
common/pruner分区剪裁算法,包括 internal column pruner、limiter pruner、page pruner、topn pruner、range pruner。
common/table_meta表 meta 的数据结构定义。
hivehive 表的交互
icebergiceberg 交互
information_schema、system系统表定义
memory、null、random用于开发和测试的引擎
view视图相关
stagestage 数据源的读取
parquet把 parquet 文件作为数据源
fusefuse 引擎模块
fuse/src/iotable meta、index、block 的读写 IO 交互
fuse/src/pruningfuse 分区裁剪
fuse/src/statisticscolumn statistics 和 cluster statistics 等统计信息
fuse/src/table_functionstable function 实现
fuse/src/operationfuse 引擎对 table trait 方法的具体实现。并包含了如 ReadSource、CommitSink 等 processor 算子的定义
+

Read Partitions

+

以下以 fuse 引擎中 read partitions 的实现流程为例,简要分析 Storage 相关源码。

+

Partitions 的定义位于 query/catalog/src/plan/partition.rs

+
pub struct Partitions {
+    // partitions 的分发类型。
+    pub kind: PartitionsShuffleKind,
+    // 一组实现了 PartInfo 接口的 partition,
+    pub partitions: Vec<PartInfoPtr>,
+    // partitions 是否为 lazy。
+    pub is_lazy: bool,
+}
+
+

Table 接口中的 read_partitions 通过分析查询中的过滤条件,剪裁掉不需要的分区,返回可能满足条件的 Partitions。

+
#[async_trait::async_trait]
+impl Table for FuseTable {
+    #[minitrace::trace]
+    #[async_backtrace::framed]
+    async fn read_partitions(
+        &self,
+        ctx: Arc<dyn TableContext>,
+        push_downs: Option<PushDownInfo>,
+        dry_run: bool,
+    ) -> Result<(PartStatistics, Partitions)> {
+        self.do_read_partitions(ctx, push_downs, dry_run).await
+    }
+}
+
+

Fuse 引擎会以 segment 为单位构建 lazy 类型的 FuseLazyPartInfo。通过这种方式,prune_snapshot_blocks 可以下推到 pipeline 初始化阶段执行,特别是在分布式集群模式下,可以有效提高剪裁执行效率。

+
pub struct FuseLazyPartInfo {
+    // segment 在 snapshot 中的索引位置。
+    pub segment_index: usize,
+    pub segment_location: Location,
+}
+
+

分区剪裁流程的实现位于 query/storages/fuse/src/pruning/fuse_pruner.rs 文件中,具体流程如下:

+
    +
  1. 基于 push_downs 条件构造各类剪裁器(pruner),并实例化 FusePruner
  2. +
  3. 调用 FusePruner 中的 pruning 方法,创建 max_concurrency 个分批剪裁任务。每个批次包括多个 segment 位置,首先根据 internal_column_pruner 筛选出无需的 segments,再读取 SegmentInfo,并根据 segment 级别的 MinMax 索引进行范围剪裁。
  4. +
  5. 读取过滤后的 SegmentInfo 中的 BlockMetas,并按照 internal_column_prunerlimit_prunerrange_prunerbloom_prunerpage_pruner 等算法的顺序,剔除无需的 blocks。
  6. +
  7. 执行 TopNPrunner 进行过滤,从而得到最终剪裁后的 block_metas
  8. +
+
pub struct FusePruner {
+    max_concurrency: usize,
+    pub table_schema: TableSchemaRef,
+    pub pruning_ctx: Arc<PruningContext>,
+    pub push_down: Option<PushDownInfo>,
+    pub inverse_range_index: Option<RangeIndex>,
+    pub deleted_segments: Vec<DeletedSegmentInfo>,
+}
+
+pub struct PruningContext {
+    pub limit_pruner: Arc<dyn Limiter + Send + Sync>,
+    pub range_pruner: Arc<dyn RangePruner + Send + Sync>,
+    pub bloom_pruner: Option<Arc<dyn BloomPruner + Send + Sync>>,
+    pub page_pruner: Arc<dyn PagePruner + Send + Sync>,
+    pub internal_column_pruner: Option<Arc<InternalColumnPruner>>,
+    // Other Fields ...
+}
+
+impl FusePruner {
+    pub async fn pruning(
+        &mut self,
+        mut segment_locs: Vec<SegmentLocation>,
+        delete_pruning: bool,
+    ) -> Result<Vec<(BlockMetaIndex, Arc<BlockMeta>)>> {
+        ...
+    }
+}
+
+

剪裁结束后,以 Block 为单位构造 FusePartInfo,生成 partitions,接着调用 set_partitions 方法将 partitions 注入 QueryContext 的分区队列中。在执行任务时,可以通过 get_partition 方法从队列中取出。

+
pub struct FusePartInfo {
+    pub location: String, 
+    pub create_on: Option<DateTime<Utc>>,
+    pub nums_rows: usize,
+    pub columns_meta: HashMap<ColumnId, ColumnMeta>,
+    pub compression: Compression,
+    pub sort_min_max: Option<(Scalar, Scalar)>,
+    pub block_meta_index: Option<BlockMetaIndex>,
+}
+
+

Conclusion

+

Databend 的存储引擎设计采用了抽象接口的方式,具有高度的可扩展性,可以很方便地支持多种不同的存储引擎。Storage 模块的主要职责是实现 Table 接口的方法,其中 Fuse 引擎部分尤为关键。

+

通过对数据的并行处理,以及数据剪裁等手段,可以有效地提高数据的处理效率。鉴于篇幅限制,本文仅对读取分区的流程进行了简单阐述,更深入的解析将在后续的文章中逐步展开。

+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 多云转晴:Databend 的天空计算之路 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 第一弹 - minibend 简介 → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/the-basics/about-index/index.html b/docs/the-basics/about-index/index.html new file mode 100644 index 0000000..d153d21 --- /dev/null +++ b/docs/the-basics/about-index/index.html @@ -0,0 +1,588 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +索引 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

索引

+

数据库中的索引就像书后附录提供的索引一样,有助于快速定位记录并读取需要的信息。本文简要梳理了索引相关的内容。

+

基本概念

+

在很多书籍的附录中会包含一份索引,记录了关键字/词组/主题在书中出现的页码。如果需要查阅特定主题的内容,只需要打开索引检索主题,并翻到对应页码即可。

+

那么对于数据库来说,如果计划访问具有某些特征的记录,则可以通过索引找出其所对应的数据块,通过读取数据块得到对应的记录。

+

索引的优缺点

+

在上面的过程中,可以清楚看到索引的一个显著 优点 是:加快数据查询的速度。

+

但是,设想你正在撰写一本技术书,并不得不为数百上千个关键字建立索引。显而易见,索引也有一些 缺点 :创建和维护索引会耗费一定的时间,且索引还需要占据额外的空间。

+

索引的基本类型

+

书籍中的索引常常会按关键字的字典序进行排列,对应到数据库中,这类基于值的顺序编排的索引被称作 顺序索引

+

随着索引量的增大,即便是按顺序查找仍然会需要耗费大量时间,通过引入一些相对更复杂的索引技术,可以解决此问题。常见的是 散列索引 ,基于散列函数将值分布到若干散列桶。

+

常见的评估指标

+

数据库系统中往往会存在不止一种索引技术,不同数据库系统之间的索引技术可能也不同。评估并对比不同的索引可以考虑以下几个指标:

+
    +
  • 访问类型:能支持哪些访问类型。
  • +
  • 访问时间:找到特定数据项 / 数据项集所需的时间。
  • +
  • 插入时间:插入一个新数据项所需的时间。
  • +
  • 删除时间:删除一个数据项所需的时间。
  • +
  • 空间开销:索引结构占用的额外空间。
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 简介 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 使用 Rust 构建云原生数仓 Databend → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/the-basics/distributed/index.html b/docs/the-basics/distributed/index.html new file mode 100644 index 0000000..9ef5b38 --- /dev/null +++ b/docs/the-basics/distributed/index.html @@ -0,0 +1,579 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +分布式 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

分布式

+

越来越多的数据库产品提供分布式解决方案,那么什么是分布式?

+

概念

+

分布式系统是由一组计算节点通过网络链接组成的服务系统,作为整体对外提供服务。由于计算节点可能存在异构、分区等问题,还需要解决系统中存在的通讯和共识问题。

+

特性

+

作为一个松耦合的系统,分布式系统具有以下特点:

+
    +
  • 分布性:节点在地理位置上可能是分散的。
  • +
  • 对等性:分布式系统各个节点是对等的,节点不仅可以协同完成任务,同时也可以自治处理任务。
  • +
  • 并发性:分布式系统中的各个节点可以并发操作共享资源。
  • +
+

辨析

+

从某种视角上看,分布式系统与 MPP 系统有着惊人的相似。比如:通过网络连接、对外作为整体提供服务、计算节点拥有资源等。但是这两种架构仍然会有一些不同。

+
    +
  • 从设计目标上看,分布式系统致力于改善系统的可靠性和可用性,而 MPP 系统需要充分利用计算节点的并行能力从而提高整体性能。
  • +
  • 具体到实现上,分布式系统降低了对网络的需求,采用局域网或广域网相连,拓展性进一步增强。而 MPP 系统为了充分利用计算节点的处理能力,依赖高速网络进行通讯。
  • +
  • 同时,由于节点地位不同,分布式系统除了协同执行任务之外,还具有自治执行任务的能力;而 MPP 系统则专注于任务的协同执行。
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 简介 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 使用 Rust 构建云原生数仓 Databend → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/the-basics/executor-in-query-process/index.html b/docs/the-basics/executor-in-query-process/index.html new file mode 100644 index 0000000..234dde8 --- /dev/null +++ b/docs/the-basics/executor-in-query-process/index.html @@ -0,0 +1,691 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +查询执行 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

查询执行

+

查询在数据库系统中的调度与执行方式同样会深刻影响到数据库的性能表现。本文简要梳理了查询流程中与执行相关的内容,希望能够帮助大家更好理解查询引擎的工作原理。

+

查询的基本流程

+

首先一起来回顾一下查询的基本流程。

+

Query Steps

+

如上图所示,查询往往需要经历下述几个阶段:

+
    +
  1. 解析 SQL 语法,形成 AST(抽象语法树)。
  2. +
  3. 通过 Binder 对其进行语义分析,并且生成一个初始的 Logical Plan(逻辑计划)。
  4. +
  5. 得到初始的 Logical Plan 后,优化器会对其进行改写和优化,最终生成一个可执行的 Physical Plan 。
  6. +
  7. 通过 Optimizer 生成 Physical Plan 后,将其翻译成可执行的 Pipeline 。
  8. +
  9. Pipeline 则会交由 Processor 执行框架进行计算。
  10. +
+

从 Volcano Model 开始

+

1990 年发表的 Volcano, an Extensible and Parallel Query Evaluation System 中提出 Volcano Model 并为人熟知。

+

Volcano

+

Volcano 是一种基于行的流式迭代模型,简单而又优美。拉取数据的控制命令从最上层的算符依次传递到执行树的最下层,与数据的流动方向正好相反。

+

优点

+
    +
  • 对 Data Stream 进行抽象并提供一系列标准接口,各个算符之间充分解耦。模型简单,易于扩展。
  • +
  • 框架完成了算符组合和数据处理的整体流程,算符的实现只要关注数据的处理流程。与执行策略隔离,具有很强的灵活性。
  • +
+

缺点

+
    +
  • 采用 Pull 模型拉取数据,数据在算符之间的流动需要额外的控制操作,所以存在大量冗余的控制指令。
  • +
  • 迭代器模型意味着算符之间需要大量 next() 调用,虚函数嵌套对分支预测并不友好,会破坏 CPU 流水线并造成 Cache 和 TLB 失效。
  • +
+

小结

+

时至今日,内存容量突飞猛进,数据可以直接存放在内存中,负载从 IO bound 转向 memory bound ;而 CPU 单核效率面临瓶颈,多核能力日益重要,更需要关注 CPU 执行效率。向量化执行/编译执行等方式开始绽放异彩。

+

尽管 Volcano Model 受限于当时尚未成熟的硬件环境(CPU 并行能力不足、内存容量小且 IO 效率低下),但它的设计仍然值得借鉴,在现代一些 state of the art 执行器方案中,仍然可以看到它的影子。

+

迈向 Morsel-Driven Parallelism

+

Databend 的执行器部分主要借鉴了 Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age 这篇论文。

+

Morsel-Driven Parallelism

+

面向多核架构

+

Morsel 有「小块」的意思,意味着任务会被拆解成大小合适、可动态调整的一系列算子,比如表达式的计算、聚合函数的计算等。而 Morsel-Driven Parallelism 提供了一种自适应的调度执行方案,在运行时确定任务的并行度,按流水线的方式执行操作,并通过调度策略来尽量保证数据的本地化,在实现 load balance 的同时最小化跨域数据访问。

+

合理分发任务

+

汽车的流水线生产需要各个部门、各种零件配合,查询的高效执行也离不开不同算子的组合。

+

这时就需要引入一个调度器(Dispatcher)为并行的 Pipeline 控制分配计算资源。Dispatcher 维护着各个查询传递而来的 Pipeline Jobs,每个任务都相当于查询的一个子计划,会对应到底层需要处理的数据。在线程池请求分发 Task 时,Dispatcher 会遵循调度策略,根据任务执行状态、资源使用情况等要素,来决定什么时候 CPU 该执行哪个 Transform 。

+

Morsel Wise

+

Morsel-Driven Parallelism 的研究不仅仅关注执行框架的改进,还涵盖一些特定算法的并行优化,比如 Hash Join、Grouping/Aggregation 以及排序。

+

在 Morsel Wise 思想的指导下,Morsel-Driven Parallelism 执行框架解决了多核时代中负载均衡、线程同步、本地内存访问以及资源弹性调度的问题。

+

再谈列式存储与向量化执行

+

向量化执行自 MonetDB/X100(Vectorwise)开始流行,MonetDB/X100: Hyper-Pipelining Query Execution 这篇论文也成为了必读之作。而在 2010 年之后的 OLAP 系统,基本上都是按列式存储进行数据组织的。

+

Column Based

+

左图可以看作列式存储的一个样本,同一列的数据在内存中形成一个连续的结构。

+

列式存储

+

OLAP 系统需要处理的查询通常涉及大量的数据,采用列式存储方案在提高 IO 效率方面具备天然优势。

+
    +
  • 只需要读取需要的列,无需经 IO 读取其余列,从而避免不必要的 IO 开销。
  • +
  • 同一列的数据中往往存在大量的重复项,压缩率会非常高,进一步节约 IO 资源。
  • +
+

向量化执行

+

向量化执行的优势在于可以充分利用 CPU 缓存,从而设计更为高效的分析查询引擎。

+
    +
  • 数据在内存中连续;由于做到按需读取,还能减少不必要的缓存占用。
  • +
  • 减少处理数据时需要传递的数据量,并摊匀不同算符之间调用的开销。
  • +
+

SIMD 优化

+

谈到向量化执行,不可避免要用到 SIMD(单指令多数据)。传统的方式是查询指令集,然后手工编写指令;而在 Databend 中,采用以下方式:

+
    +
  • 利用 Rust 语言标准库,std::simd 提供关于 SIMD 指令的抽象封装,可以编写易于理解的代码。
  • +
  • 自动向量化,通过优化代码逻辑,削减循环中的分支预测,充分利用编译器的能力。
  • +
+

关于 Databend 查询执行的一些问答

+
+

以下内容整理自 @fkuner@zhang2014 的一次对话。

+
+
    +
  1. +

    Databend 中如何保证 numa-local ?

    +

    答:numa-local 在 aggregator processor 中是 core 独享的。pipelines size 和 executor worker size 1:1 对应也是为了numa local 。在调度时,尽量不会切换线程。一个任务从头调度到尾,将新产生的支线任务放入全局调度。

    +
  2. +
  3. +

    Pipeline 如果需要等待 IO 是如何调度的?

    +

    答:Databend 通过感知数据状态来调度 Pipeline,如果数据没有准备好不会调度。至于 IO ,会被调度到 global io runtime 中,通过 Rust Async 阻塞等待。

    +
  4. +
  5. +

    任务、Pipeline 和 Processor 的对应关系是怎样的?

    +

    答:论文中的模型是:一个任务处理一个 Pipline ,而一个 Pipeline 可以由多个 Processor 组成。而 Databend 可以在 Processor 级别做任务窃取,任务切割到 Processor 级别可切分的情况下,调度是更灵活的。虽然是在调度器中调度Processor,但这个 Processor 在运行状态中具体对应到的就是一个 Data Block 。类似论文中 Pipeline 的 Job 切分。

    +
  6. +
  7. +

    Databend 中对 numa-local 倾向性的调度处理是如何做的?

    +

    答:理想状态下执行线程应该互不干扰,但考虑到任务可能存在倾斜。当其中某个线程提前完成任务时,为了加速整个执行流程可能需要该线程窃取剩下的任务。在调度时,执行器会存在一个 local context ,不会在线程间存在任何共享。

    +
  8. +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 简介 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 使用 Rust 构建云原生数仓 Databend → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/the-basics/index.html b/docs/the-basics/index.html new file mode 100644 index 0000000..c21048b --- /dev/null +++ b/docs/the-basics/index.html @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +基础导览 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+ +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/the-basics/mpp/index.html b/docs/the-basics/mpp/index.html new file mode 100644 index 0000000..274bda3 --- /dev/null +++ b/docs/the-basics/mpp/index.html @@ -0,0 +1,578 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +大规模并行处理 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

大规模并行处理

+

大规模并行处理是大数据计算引擎的一个重要特性,可以提供高吞吐、低时延的计算能力。那么,当我们在讨论大规模并行处理时,究竟在讨论什么?

+

背景

+

企业或个人都可能会收集和存储大量的数据,特别是近年来大数据技术的兴起,人们拥有更多接触数据和利用数据的机会和意愿,那么随着数据量的增长,对存储容量和计算能力的要求也进一步提高了。

+

概要

+

大规模并行处理(MPP,Massively Parallel Processing)意味着可以由多个计算节点(处理器)协同处理程序的不同部分,而每个计算节点都可能具备独立的系统资源(磁盘、内存、操作系统)。

+

计算节点将工作拆分成易于管理、调度和执行的任务执行,通过添加额外的计算节点可以完成水平拓展。随着计算节点数目的增加,对数据的查询处理速度就越快,从而减少大数据集上处理复杂查询所需的时间。

+

特性

+

采用大规模并行处理架构设计的系统往往具备以下特性:

+
    +
  • 任务并行执行
  • +
  • 数据分布式存储
  • +
  • 分布式计算
  • +
  • 私有资源
  • +
  • 水平拓展
  • +
  • Shared Nothing
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 简介 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 使用 Rust 构建云原生数仓 Databend → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/the-basics/query-optimization/index.html b/docs/the-basics/query-optimization/index.html new file mode 100644 index 0000000..468406c --- /dev/null +++ b/docs/the-basics/query-optimization/index.html @@ -0,0 +1,668 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +查询优化 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

查询优化

+

查询优化是数据库系统的一个重要话题。本文介绍了查询优化的相关概念及发展历史,Cascades 优化器以及云数仓所面临的查询优化挑战。

+
+

本文根据 @leiysky 的分享整理而来,略去了查询优化的细节,详细的 PPT 可以参考 SQL Processing & Query Optimization

+
+

查询优化

+

什么是查询优化

+

查询优化指的是为给定的查询选择最佳执行计划。

+

那么什么样的执行计划称得上是最佳计划呢?

+
    +
  • 更快的速度(更低的延迟),这是一个非常直观的评价标准。
  • +
  • 在 OLTP 场景下,则更强调性价比。
  • +
  • 而对于 OLAP 场景,则追求更的高吞吐量。
  • +
  • 从商业口号的角度看,可能偏向于一次“正确”的技术选型。
  • +
+

为什么可以优化 SQL 查询

+

目前有两种主要的查询优化方案,一种是基于关系代数和算法的等价优化方案,一种是基于评估成本的优化方案。

+

根据命名,不难看出优化的灵感来源和这两种方案在优化上的取舍。

+

如何进行查询优化

+

查询优化通常包含以下四个步骤:

+
    +
  • 构建框架来列举可能的计划
  • +
  • 编写转换规则
  • +
  • 引入成本模型来评估不同的计划
  • +
  • 选择最理想的计划
  • +
+

查询优化的历史

+

IBM System R's Optimizer

+

世界上第一个查询优化器是 IBM System R 的优化器。

+

其建立背景是:

+
    +
  • 磁盘比内存慢得多,查找数据的开销非常大。
  • +
  • 内存很小。
  • +
  • 单 CPU 核心(不存在并行)。
  • +
+

PostgreSQL's Optimizer

+

PostgreSQL 是世界上最成功的开源 RDBMS 之一,有着悠久的历史(1996 年首次发布)。

+
    +
  • 引入成本模型。
  • +
  • 基于动态规划的 Join 重排。
  • +
  • Genetic Query Optimizer:基于遗传算法的查询优化器。
  • +
+

SQL Server's Optimizer

+

SQL Server,由微软和 Sybase 在 20 世纪 90 年代开发的商业 RDBMS 。

+

Goetz Graefe(Volcano/Cascades的作者)为 SQL Server 设计了 Cascades 查询优化框架。

+

该优化器框架已被广泛用于微软开发的不同查询系统(如 SQL Server、SQL Server PDW、Cosmos SCOPE、Synapse)。

+

世界上最好的查询优化器(也许)。

+

Volcano/Cascades 优化器框架

+

枚举计划并评估成本的探索框架。

+ +

Cascades 之禅

+
    +
  • 自顶向下探索
  • +
  • 模式匹配
  • +
  • 基于规则“Rule-based”
  • +
  • 记忆化
  • +
+

开源的 Cascades 实现

+
    +
  • Apache Calcite: a Volcano/Cascades style optimizer framework, widely used in Apache world(e.g. Drill, Flink)
  • +
  • GreenPlum Orca: optimizer component of GreenPlum, also used by HAWQ, Hologres, Alicloud ADB
  • +
  • CockroachDB's Cascades optimizer
  • +
+

云数仓所面临的查询优化挑战

+
    +
  • 对非常大的数据集进行成本估算
  • +
  • 复杂的成本模型因子
  • +
  • 不同的优化方法,取决于存储系统的设计
  • +
  • 可测试性、可追踪性、可调试性
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 简介 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 使用 Rust 构建云原生数仓 Databend → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/docs/the-basics/storage/index.html b/docs/the-basics/storage/index.html new file mode 100644 index 0000000..a4bbc1e --- /dev/null +++ b/docs/the-basics/storage/index.html @@ -0,0 +1,639 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +存储 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+ + + + + + + + +
+

存储

+

在这篇文章中,概述了存储的一些基本内容并简要回答「数据库如何进行存储」,为大家了解存储系统提供一个基本的视角。

+

存储介质与存储系统

+

存储介质

+

综合存储介质的性能和成本,大致可以得到一个如下图所示的层次结构:

+

Storage Level

+

常见存储介质

+
    +
  • 缓存:最快且最贵,通常情况下由计算机系统硬件进行管理。
  • +
  • 主内存:存放可处理的数据,断电后丢失数据。
  • +
  • 闪存:断电数据不丢失,速度比磁盘快很多,相同容量下成本远低于主内存。
  • +
  • 磁盘:长期联机数据存储的主要介质。
  • +
  • 光盘:数据通过光学方法存储到器件上,并利用激光器读取。
  • +
  • 磁带:通常用于备份和归档数据,只能从头顺序访问。
  • +
+

根据数据访问频率的划分

+

缓存和主内存是基本存储,只有存放在基本存储中的数据才能被直接访问和处理;其他正在使用/将要使用的数据往往会存放在闪存和磁盘上,这两类存储介质可以视作是二级存储,也就是联机存储;对于备份或不常访问的数据,则会依赖光盘和磁带进行存储,它们也被称作是三级存储或者是脱机存储。

+

根据存储易失性的划分

+

缓存和主内存通常被认为是易失性存储,这表示它们在断电后会丢失所有数据;而其他几个视作非易失性存储。为了保存数据,必须将数据写入到非易失性存储中。

+

存储系统

+

DAS 直接连接存储,是一种以服务器为中心的存储方案,对外和多个主机连接,对内连接多个磁盘,并实现 RAID 技术,形成一个磁盘阵列。

+

SAN 存储区域网,通过专用高速网将一个或多个网络存储设备和服务器连接起来的专用存储系统。通常情况下,SAN 方案会采用 RAID 技术提供一个的大逻辑存储视图。

+

NAS 附网存储系统是另一类集成系统、存储和网络技术的方案,可以看作是一个装有优化的文件系统和瘦操作系统的专用数据存储服务器,提供跨平台的文件共享功能。通过 NFS、CIFS 等协议为用户提供一个文件系统视图。

+

云存储

+

这一部分仅仅是作为一个引子,帮助我们将视角从传统的物理机进一步扩展到云。

+

云存储能够支持远程保存数据和文件,并通过网络连接进行访问。云供应商负责托管、和维护相关的基础设施并提供访问性保证。不仅可以节约拓展物理器件所带来的人力物力消耗,并能够提供更好的弹性以便于即时增减容量,还支持按需按量付费从而做到更好的成本管理与控制。

+

常见的云存储形式

+
    +
  • 文件存储:文件存储方法将数据保存在分层目录结构中。数据可以保留自己的格式,并且得益于层级结构,可以方便进行检索。
  • +
  • 对象存储:将数据作为对象管理,每个对象包含数据、关联的元数据和标识。无需按层级结构归档,支持元数据定制,能够提供经济高效的存储。
  • +
  • 块存储:数据被组织成称为“块”的大型卷。 每个块代表一个单独的硬盘驱动器。云存储供应商使用块来分割多个存储节点中的大量数据。块存储的 IO 延迟低,性能更为理想。
  • +
+

优点

+
    +
  • 弹性:弹性意味着存储容量可以根据业务/数据规模即时进行放缩,这规避了本地存储常见的容量限制问题。同时结合灵活的计费模型,可以提供更经济的存储方案。
  • +
  • 非现场:一方面,不再需要投入人力物力到存储系统的建设和维护;另一方面,由于供应商为访问性提供保障,则无需担心业务因自己场所的状态陷入不可用的境地。
  • +
+

缺点

+
    +
  • 延迟:由于流量拥堵和其他可能存在的访问限制,往往会导致与云存储之间出现访问延迟。
  • +
  • 合规:考虑到数据的安全性,和所处行业的数据隐私和归档法规,云存储并不总能满足合规性要求,需要根据实际情况选择合适的供应商。
  • +
+

改善存储的典型方案

+

性能

+
    +
  • 缓存:缓存的目的是减少磁盘访问次数,通过尽可能最大化数据已经存储在主内存中的几率来减少需要传输的数据量。
  • +
  • 并行:并行不光能够为小数据访问提供负载均衡并保证吞吐量,也可以并行执行大的访问操作从而改善响应时间。常见的提高并行的方案是对数据进行拆分。
  • +
+

可靠性

+
    +
  • 冗余:冗余意味着需要存储有利于在发生存储故障时重建丢失数据的那些信息。这样一来,即便出现小规模的存储故障,数据也不会丢失,从而延长发生故障的平均时间。一种简单的冗余方案是直接备份全部数据。
  • +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ← 简介 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ 使用 Rust 构建云原生数仓 Databend → +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+
+ + + + + + + + + + + + + + diff --git a/doks.png b/doks.png new file mode 100644 index 0000000..70d8c78 Binary files /dev/null and b/doks.png differ diff --git a/doks.svg b/doks.svg new file mode 100644 index 0000000..4631bc5 --- /dev/null +++ b/doks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/favicon-16x16.png b/favicon-16x16.png new file mode 100644 index 0000000..8fcd422 Binary files /dev/null and b/favicon-16x16.png differ diff --git a/favicon-32x32.png b/favicon-32x32.png new file mode 100644 index 0000000..21378a4 Binary files /dev/null and b/favicon-32x32.png differ diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..9e2fc8d Binary files /dev/null and b/favicon.ico differ diff --git a/fonts/vendor/jost/jost-v4-latin-500.woff b/fonts/vendor/jost/jost-v4-latin-500.woff new file mode 100644 index 0000000..eab8d1b Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-500.woff differ diff --git a/fonts/vendor/jost/jost-v4-latin-500.woff2 b/fonts/vendor/jost/jost-v4-latin-500.woff2 new file mode 100644 index 0000000..92349d1 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-500.woff2 differ diff --git a/fonts/vendor/jost/jost-v4-latin-500italic.woff b/fonts/vendor/jost/jost-v4-latin-500italic.woff new file mode 100644 index 0000000..7a8ed95 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-500italic.woff differ diff --git a/fonts/vendor/jost/jost-v4-latin-500italic.woff2 b/fonts/vendor/jost/jost-v4-latin-500italic.woff2 new file mode 100644 index 0000000..c6b9d56 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-500italic.woff2 differ diff --git a/fonts/vendor/jost/jost-v4-latin-700.woff b/fonts/vendor/jost/jost-v4-latin-700.woff new file mode 100644 index 0000000..57390f5 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-700.woff differ diff --git a/fonts/vendor/jost/jost-v4-latin-700.woff2 b/fonts/vendor/jost/jost-v4-latin-700.woff2 new file mode 100644 index 0000000..584073a Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-700.woff2 differ diff --git a/fonts/vendor/jost/jost-v4-latin-700italic.woff b/fonts/vendor/jost/jost-v4-latin-700italic.woff new file mode 100644 index 0000000..e23eedf Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-700italic.woff differ diff --git a/fonts/vendor/jost/jost-v4-latin-700italic.woff2 b/fonts/vendor/jost/jost-v4-latin-700italic.woff2 new file mode 100644 index 0000000..e56d892 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-700italic.woff2 differ diff --git a/fonts/vendor/jost/jost-v4-latin-italic.woff b/fonts/vendor/jost/jost-v4-latin-italic.woff new file mode 100644 index 0000000..5dcd111 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-italic.woff differ diff --git a/fonts/vendor/jost/jost-v4-latin-italic.woff2 b/fonts/vendor/jost/jost-v4-latin-italic.woff2 new file mode 100644 index 0000000..9346c1d Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-italic.woff2 differ diff --git a/fonts/vendor/jost/jost-v4-latin-regular.woff b/fonts/vendor/jost/jost-v4-latin-regular.woff new file mode 100644 index 0000000..4f24758 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-regular.woff differ diff --git a/fonts/vendor/jost/jost-v4-latin-regular.woff2 b/fonts/vendor/jost/jost-v4-latin-regular.woff2 new file mode 100644 index 0000000..7f0bf75 Binary files /dev/null and b/fonts/vendor/jost/jost-v4-latin-regular.woff2 differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..ddacbe1 --- /dev/null +++ b/index.html @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Databend 内幕大揭秘 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+

Databend 内幕大揭秘

+
+
+

Databend 一同探秘数据库系统

+ 即刻启程 +

CC-BY 4.0 & Apache 2.0 GitHub

+
+
+
+
+
+ +
+
+
+ + +
+

基础导览

+

化繁为简,精心编排。
专设基础知识导读,即刻开启无痛学习。

+
+ +
+

特性探索

+

能用 -> 易用 -> 好用。
以 Databend 为例,揭示现代云数仓特性。

+
+ +
+

源码解读

+

在 Real World 中寻找版本答案。
透过 Databend 深入数据库设计与实现。

+
+ +
+

开源贡献

+

为 Databend 添砖加瓦。
一起学习如何为开源现代云数仓做贡献。

+
+ +
+

实战演练

+

从 0 到 1 ,构建属于你的实时数仓。
从 Minibend 开始数据库内核研发之旅。

+
+ +
+

专题研讨

+

不定期的论文/技术研讨会。
关注数据库、分布式等相关领域新动态。

+
+ + +
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..8c87d5f --- /dev/null +++ b/index.js @@ -0,0 +1,146 @@ +var suggestions = document.getElementById('suggestions'); +var userinput = document.getElementById('userinput'); + +document.addEventListener('keydown', inputFocus); + +function inputFocus(e) { + + if (e.keyCode === 191 ) { + e.preventDefault(); + userinput.focus(); + } + + if (e.keyCode === 27 ) { + userinput.blur(); + suggestions.classList.add('d-none'); + } + +} + +document.addEventListener('click', function(event) { + + var isClickInsideElement = suggestions.contains(event.target); + + if (!isClickInsideElement) { + suggestions.classList.add('d-none'); + } + +}); + +/* +Source: + - https://dev.to/shubhamprakash/trap-focus-using-javascript-6a3 +*/ + +document.addEventListener('keydown',suggestionFocus); + +function suggestionFocus(e){ + + const focusableSuggestions= suggestions.querySelectorAll('a'); + const focusable= [...focusableSuggestions]; + const index = focusable.indexOf(document.activeElement); + + let nextIndex = 0; + + if (e.keyCode === 38) { + e.preventDefault(); + nextIndex= index > 0 ? index-1 : 0; + focusableSuggestions[nextIndex].focus(); + } + else if (e.keyCode === 40) { + e.preventDefault(); + nextIndex= index+1 < focusable.length ? index+1 : index; + focusableSuggestions[nextIndex].focus(); + } + +} + + +/* +Source: + - https://github.com/nextapps-de/flexsearch#index-documents-field-search + - https://raw.githack.com/nextapps-de/flexsearch/master/demo/autocomplete.html +*/ + +(function(){ + + var index = new FlexSearch({ + preset: 'score', + cache: true, + doc: { + id: 'id', + field: [ + 'title', + 'description', + 'content', + ], + store: [ + 'href', + 'title', + 'description', + ], + }, + }); + + var docs = [ + {{ range $index, $page := (where .Site.Pages "Section" "docs") -}} + { + id: {{ $index }}, + href: "{{ .RelPermalink | relURL }}", + title: {{ .Title | jsonify }}, + description: {{ .Params.description | jsonify }}, + content: {{ .Content | jsonify }} + }, + {{ end -}} + ]; + + index.add(docs); + + userinput.addEventListener('input', show_results, true); + suggestions.addEventListener('click', accept_suggestion, true); + + function show_results(){ + + var value = this.value; + var results = index.search(value, 5); + var entry, childs = suggestions.childNodes; + var i = 0, len = results.length; + + suggestions.classList.remove('d-none'); + + results.forEach(function(page) { + + entry = document.createElement('div'); + + entry.innerHTML = ''; + + a = entry.querySelector('a'), + t = entry.querySelector('span:first-child'), + d = entry.querySelector('span:nth-child(2)'); + + a.href = page.href; + t.textContent = page.title; + d.textContent = page.description; + + suggestions.appendChild(entry); + + }); + + while(childs.length > len){ + + suggestions.removeChild(childs[i]) + } + + } + + function accept_suggestion(){ + + while(suggestions.lastChild){ + + suggestions.removeChild(suggestions.lastChild); + } + + return false; + } + +}()); diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..2fbc228 --- /dev/null +++ b/js/main.js @@ -0,0 +1,14 @@ +// Set darkmode +document.getElementById('mode').addEventListener('click', () => { + + document.body.classList.toggle('dark'); + localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light'); + +}); + +// enforce local storage setting but also fallback to user-agent preferences +if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia("(prefers-color-scheme: dark)").matches)) { + + document.body.classList.add('dark'); + +} diff --git a/js/search.js b/js/search.js new file mode 100644 index 0000000..7c5c745 --- /dev/null +++ b/js/search.js @@ -0,0 +1,317 @@ +var suggestions = document.getElementById('suggestions'); +var userinput = document.getElementById('userinput'); + +document.addEventListener('keydown', inputFocus); + +function inputFocus(e) { + + if (e.keyCode === 191 + && document.activeElement.tagName !== "INPUT" + && document.activeElement.tagName !== "TEXTAREA") { + e.preventDefault(); + userinput.focus(); + } + + if (e.keyCode === 27 ) { + userinput.blur(); + suggestions.classList.add('d-none'); + } + +} + +document.addEventListener('click', function(event) { + + var isClickInsideElement = suggestions.contains(event.target); + + if (!isClickInsideElement) { + suggestions.classList.add('d-none'); + } + +}); + +/* +Source: + - https://dev.to/shubhamprakash/trap-focus-using-javascript-6a3 +*/ + +document.addEventListener('keydown',suggestionFocus); + +function suggestionFocus(e){ + const focusableSuggestions= suggestions.querySelectorAll('a'); + if (suggestions.classList.contains('d-none') + || focusableSuggestions.length === 0) { + return; + } + const focusable= [...focusableSuggestions]; + const index = focusable.indexOf(document.activeElement); + + let nextIndex = 0; + + if (e.keyCode === 38) { + e.preventDefault(); + nextIndex= index > 0 ? index-1 : 0; + focusableSuggestions[nextIndex].focus(); + } + else if (e.keyCode === 40) { + e.preventDefault(); + nextIndex= index+1 < focusable.length ? index+1 : index; + focusableSuggestions[nextIndex].focus(); + } + +} + +/* +Source: + - https://github.com/nextapps-de/flexsearch#index-documents-field-search + - https://raw.githack.com/nextapps-de/flexsearch/master/demo/autocomplete.html + - http://elasticlunr.com/ + - https://github.com/getzola/zola/blob/master/docs/static/search.js +*/ +(function(){ + var index = elasticlunr.Index.load(window.searchIndex); + userinput.addEventListener('input', show_results, true); + suggestions.addEventListener('click', accept_suggestion, true); + + function show_results(){ + var value = this.value.trim(); + var options = { + bool: "OR", + fields: { + title: {boost: 2}, + body: {boost: 1}, + } + }; + var results = index.search(value, options); + + var entry, childs = suggestions.childNodes; + var i = 0, len = results.length; + var items = value.split(/\s+/); + suggestions.classList.remove('d-none'); + + results.forEach(function(page) { + if (page.doc.body !== '') { + entry = document.createElement('div'); + + entry.innerHTML = ''; + + a = entry.querySelector('a'), + t = entry.querySelector('span:first-child'), + d = entry.querySelector('span:nth-child(2)'); + a.href = page.ref; + t.textContent = page.doc.title; + d.innerHTML = makeTeaser(page.doc.body, items); + + suggestions.appendChild(entry); + } + }); + + while(childs.length > len){ + suggestions.removeChild(childs[i]) + } + + } + + function accept_suggestion(){ + + while(suggestions.lastChild){ + + suggestions.removeChild(suggestions.lastChild); + } + + return false; + } + + // Taken from mdbook + // The strategy is as follows: + // First, assign a value to each word in the document: + // Words that correspond to search terms (stemmer aware): 40 + // Normal words: 2 + // First word in a sentence: 8 + // Then use a sliding window with a constant number of words and count the + // sum of the values of the words within the window. Then use the window that got the + // maximum sum. If there are multiple maximas, then get the last one. + // Enclose the terms in . + function makeTeaser(body, terms) { + var TERM_WEIGHT = 40; + var NORMAL_WORD_WEIGHT = 2; + var FIRST_WORD_WEIGHT = 8; + var TEASER_MAX_WORDS = 30; + + var stemmedTerms = terms.map(function (w) { + return elasticlunr.stemmer(w.toLowerCase()); + }); + var termFound = false; + var index = 0; + var weighted = []; // contains elements of ["word", weight, index_in_document] + + // split in sentences, then words + var sentences = body.toLowerCase().split(". "); + for (var i in sentences) { + var words = sentences[i].split(/[\s\n]/); + var value = FIRST_WORD_WEIGHT; + for (var j in words) { + + var word = words[j]; + + if (word.length > 0) { + for (var k in stemmedTerms) { + if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) { + value = TERM_WEIGHT; + termFound = true; + } + } + weighted.push([word, value, index]); + value = NORMAL_WORD_WEIGHT; + } + + index += word.length; + index += 1; // ' ' or '.' if last word in sentence + } + + index += 1; // because we split at a two-char boundary '. ' + } + + if (weighted.length === 0) { + if (body.length !== undefined && body.length > TEASER_MAX_WORDS * 10) { + return body.substring(0, TEASER_MAX_WORDS * 10) + '...'; + } else { + return body; + } + } + + var windowWeights = []; + var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS); + // We add a window with all the weights first + var curSum = 0; + for (var i = 0; i < windowSize; i++) { + curSum += weighted[i][1]; + } + windowWeights.push(curSum); + + for (var i = 0; i < weighted.length - windowSize; i++) { + curSum -= weighted[i][1]; + curSum += weighted[i + windowSize][1]; + windowWeights.push(curSum); + } + + // If we didn't find the term, just pick the first window + var maxSumIndex = 0; + if (termFound) { + var maxFound = 0; + // backwards + for (var i = windowWeights.length - 1; i >= 0; i--) { + if (windowWeights[i] > maxFound) { + maxFound = windowWeights[i]; + maxSumIndex = i; + } + } + } + + var teaser = []; + var startIndex = weighted[maxSumIndex][2]; + for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) { + var word = weighted[i]; + if (startIndex < word[2]) { + // missing text from index to start of `word` + teaser.push(body.substring(startIndex, word[2])); + startIndex = word[2]; + } + + // add around search terms + if (word[1] === TERM_WEIGHT) { + teaser.push(""); + } + + startIndex = word[2] + word[0].length; + // Check the string is ascii characters or not + var re = /^[\x00-\xff]+$/ + if (word[1] !== TERM_WEIGHT && word[0].length >= 12 && !re.test(word[0])) { + // If the string's length is too long, it maybe a Chinese/Japance/Korean article + // if using substring method directly, it may occur error codes on emoji chars + var strBefor = body.substring(word[2], startIndex); + var strAfter = substringByByte(strBefor, 12); + teaser.push(strAfter); + } else { + teaser.push(body.substring(word[2], startIndex)); + } + + if (word[1] === TERM_WEIGHT) { + teaser.push(""); + } + } + teaser.push("…"); + return teaser.join(""); + } +}()); + + +// Get substring by bytes +// If using JavaScript inline substring method, it will return error codes +// Source: https://www.52pojie.cn/thread-1059814-1-1.html +function substringByByte(str, maxLength) { + var result = ""; + var flag = false; + var len = 0; + var length = 0; + var length2 = 0; + for (var i = 0; i < str.length; i++) { + var code = str.codePointAt(i).toString(16); + if (code.length > 4) { + i++; + if ((i + 1) < str.length) { + flag = str.codePointAt(i + 1).toString(16) == "200d"; + } + } + if (flag) { + len += getByteByHex(code); + if (i == str.length - 1) { + length += len; + if (length <= maxLength) { + result += str.substr(length2, i - length2 + 1); + } else { + break + } + } + } else { + if (len != 0) { + length += len; + length += getByteByHex(code); + if (length <= maxLength) { + result += str.substr(length2, i - length2 + 1); + length2 = i + 1; + } else { + break + } + len = 0; + continue; + } + length += getByteByHex(code); + if (length <= maxLength) { + if (code.length <= 4) { + result += str[i] + } else { + result += str[i - 1] + str[i] + } + length2 = i + 1; + } else { + break + } + } + } + return result; +} + +// Get the string bytes from binary +function getByteByBinary(binaryCode) { + // Binary system, starts with `0b` in ES6 + // Octal number system, starts with `0` in ES5 and starts with `0o` in ES6 + // Hexadecimal, starts with `0x` in both ES5 and ES6 + var byteLengthDatas = [0, 1, 2, 3, 4]; + var len = byteLengthDatas[Math.ceil(binaryCode.length / 8)]; + return len; +} + +// Get the string bytes from hexadecimal +function getByteByHex(hexCode) { + return getByteByBinary(parseInt(hexCode, 16).toString(2)); +} diff --git a/logo-doks.png b/logo-doks.png new file mode 100644 index 0000000..74bdfb9 Binary files /dev/null and b/logo-doks.png differ diff --git a/main.css b/main.css new file mode 100644 index 0000000..fce5e15 --- /dev/null +++ b/main.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v5.0.0-beta3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue: #0d6efd;--bs-indigo: #6610f2;--bs-purple: #5d2f86;--bs-pink: #d63384;--bs-red: #dc3545;--bs-orange: #fd7e14;--bs-yellow: #ffe000;--bs-green: #198754;--bs-teal: #20c997;--bs-cyan: #0dcaf0;--bs-white: #fff;--bs-gray: #6c757d;--bs-gray-dark: #343a40;--bs-primary: #5d2f86;--bs-secondary: #6c757d;--bs-success: #198754;--bs-info: #0dcaf0;--bs-warning: #ffe000;--bs-danger: #dc3545;--bs-light: #f8f9fa;--bs-dark: #212529;--bs-font-sans-serif: "Jost", -apple-system, blinkmacsystemfont, "Segoe UI", roboto, "Helvetica Neue", arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--bs-font-monospace: sfmono-regular, menlo, monaco, consolas, "Liberation Mono", "Courier New", monospace;--bs-gradient: linear-gradient(180deg, rgba(255,255,255,.15), rgba(255,255,255,0))}*,*::before,*::after{box-sizing:border-box}@media (prefers-reduced-motion: no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:"Jost",-apple-system,blinkmacsystemfont,"Segoe UI",roboto,"Helvetica Neue",arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#1d2d35;background-color:#fff;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(29,45,53,0)}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h6,.h6,h5,.h5,h4,.h4,h3,.h3,h2,.h2,h1,.h1{margin-top:0;margin-bottom:.5rem;font-weight:700;line-height:1.2}h1,.h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width: 1200px){h1,.h1{font-size:2.5rem}}h2,.h2{font-size:calc(1.325rem + .9vw)}@media (min-width: 1200px){h2,.h2{font-size:2rem}}h3,.h3{font-size:calc(1.3rem + .6vw)}@media (min-width: 1200px){h3,.h3{font-size:1.75rem}}h4,.h4{font-size:calc(1.275rem + .3vw)}@media (min-width: 1200px){h4,.h4{font-size:1.5rem}}h5,.h5{font-size:1.25rem}h6,.h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-bs-original-title]{text-decoration:underline dotted;cursor:help;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small,.small{font-size:.875em}mark,.mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#5d2f86;text-decoration:none}a:hover{color:#4a266b}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}pre,code,kbd,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr /* rtl:ignore */;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}thead,tbody,tfoot,tr,td,th{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width: 1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-text,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none !important}.lead{font-size:1.25rem;font-weight:400}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width: 1200px){.display-6{font-size:2.5rem}}.list-unstyled,.comment-list{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-xxl,.container-xl,.container-lg,.container-md,.container-sm{width:100%;padding-right:var(--bs-gutter-x, 24px);padding-left:var(--bs-gutter-x, 24px);margin-right:auto;margin-left:auto}@media (min-width: 576px){.container-sm,.container{max-width:540px}}@media (min-width: 768px){.container-md,.container-sm,.container{max-width:720px}}@media (min-width: 992px){.container-lg,.container-md,.container-sm,.container{max-width:960px}}@media (min-width: 1200px){.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1240px}}@media (min-width: 1400px){.container-xxl,.container-xl,.container-lg,.container-md,.container-sm,.container{max-width:1320px}}.row{--bs-gutter-x: 48px;--bs-gutter-y: 0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y)*-1);margin-right:calc(var(--bs-gutter-x)/-2);margin-left:calc(var(--bs-gutter-x)/-2)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x)/2);padding-left:calc(var(--bs-gutter-x)/2);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:6.25%}.col-2{flex:0 0 auto;width:12.5%}.col-3{flex:0 0 auto;width:18.75%}.col-4{flex:0 0 auto;width:25%}.col-5{flex:0 0 auto;width:31.25%}.col-6{flex:0 0 auto;width:37.5%}.col-7{flex:0 0 auto;width:43.75%}.col-8{flex:0 0 auto;width:50%}.col-9{flex:0 0 auto;width:56.25%}.col-10{flex:0 0 auto;width:62.5%}.col-11{flex:0 0 auto;width:68.75%}.col-12{flex:0 0 auto;width:75%}.col-13{flex:0 0 auto;width:81.25%}.col-14{flex:0 0 auto;width:87.5%}.col-15{flex:0 0 auto;width:93.75%}.col-16{flex:0 0 auto;width:100%}.offset-1{margin-left:6.25%}.offset-2{margin-left:12.5%}.offset-3{margin-left:18.75%}.offset-4{margin-left:25%}.offset-5{margin-left:31.25%}.offset-6{margin-left:37.5%}.offset-7{margin-left:43.75%}.offset-8{margin-left:50%}.offset-9{margin-left:56.25%}.offset-10{margin-left:62.5%}.offset-11{margin-left:68.75%}.offset-12{margin-left:75%}.offset-13{margin-left:81.25%}.offset-14{margin-left:87.5%}.offset-15{margin-left:93.75%}.g-0,.gx-0{--bs-gutter-x: 0}.g-0,.gy-0{--bs-gutter-y: 0}.g-1,.gx-1{--bs-gutter-x: .25rem}.g-1,.gy-1{--bs-gutter-y: .25rem}.g-2,.gx-2{--bs-gutter-x: .5rem}.g-2,.gy-2{--bs-gutter-y: .5rem}.g-3,.gx-3{--bs-gutter-x: 1rem}.g-3,.gy-3{--bs-gutter-y: 1rem}.g-4,.gx-4{--bs-gutter-x: 1.5rem}.g-4,.gy-4{--bs-gutter-y: 1.5rem}.g-5,.gx-5{--bs-gutter-x: 3rem}.g-5,.gy-5{--bs-gutter-y: 3rem}@media (min-width: 576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:6.25%}.col-sm-2{flex:0 0 auto;width:12.5%}.col-sm-3{flex:0 0 auto;width:18.75%}.col-sm-4{flex:0 0 auto;width:25%}.col-sm-5{flex:0 0 auto;width:31.25%}.col-sm-6{flex:0 0 auto;width:37.5%}.col-sm-7{flex:0 0 auto;width:43.75%}.col-sm-8{flex:0 0 auto;width:50%}.col-sm-9{flex:0 0 auto;width:56.25%}.col-sm-10{flex:0 0 auto;width:62.5%}.col-sm-11{flex:0 0 auto;width:68.75%}.col-sm-12{flex:0 0 auto;width:75%}.col-sm-13{flex:0 0 auto;width:81.25%}.col-sm-14{flex:0 0 auto;width:87.5%}.col-sm-15{flex:0 0 auto;width:93.75%}.col-sm-16{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:6.25%}.offset-sm-2{margin-left:12.5%}.offset-sm-3{margin-left:18.75%}.offset-sm-4{margin-left:25%}.offset-sm-5{margin-left:31.25%}.offset-sm-6{margin-left:37.5%}.offset-sm-7{margin-left:43.75%}.offset-sm-8{margin-left:50%}.offset-sm-9{margin-left:56.25%}.offset-sm-10{margin-left:62.5%}.offset-sm-11{margin-left:68.75%}.offset-sm-12{margin-left:75%}.offset-sm-13{margin-left:81.25%}.offset-sm-14{margin-left:87.5%}.offset-sm-15{margin-left:93.75%}.g-sm-0,.gx-sm-0{--bs-gutter-x: 0}.g-sm-0,.gy-sm-0{--bs-gutter-y: 0}.g-sm-1,.gx-sm-1{--bs-gutter-x: .25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y: .25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x: .5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y: .5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x: 1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y: 1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x: 1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y: 1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x: 3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y: 3rem}}@media (min-width: 768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:6.25%}.col-md-2{flex:0 0 auto;width:12.5%}.col-md-3{flex:0 0 auto;width:18.75%}.col-md-4{flex:0 0 auto;width:25%}.col-md-5{flex:0 0 auto;width:31.25%}.col-md-6{flex:0 0 auto;width:37.5%}.col-md-7{flex:0 0 auto;width:43.75%}.col-md-8{flex:0 0 auto;width:50%}.col-md-9{flex:0 0 auto;width:56.25%}.col-md-10{flex:0 0 auto;width:62.5%}.col-md-11{flex:0 0 auto;width:68.75%}.col-md-12{flex:0 0 auto;width:75%}.col-md-13{flex:0 0 auto;width:81.25%}.col-md-14{flex:0 0 auto;width:87.5%}.col-md-15{flex:0 0 auto;width:93.75%}.col-md-16{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:6.25%}.offset-md-2{margin-left:12.5%}.offset-md-3{margin-left:18.75%}.offset-md-4{margin-left:25%}.offset-md-5{margin-left:31.25%}.offset-md-6{margin-left:37.5%}.offset-md-7{margin-left:43.75%}.offset-md-8{margin-left:50%}.offset-md-9{margin-left:56.25%}.offset-md-10{margin-left:62.5%}.offset-md-11{margin-left:68.75%}.offset-md-12{margin-left:75%}.offset-md-13{margin-left:81.25%}.offset-md-14{margin-left:87.5%}.offset-md-15{margin-left:93.75%}.g-md-0,.gx-md-0{--bs-gutter-x: 0}.g-md-0,.gy-md-0{--bs-gutter-y: 0}.g-md-1,.gx-md-1{--bs-gutter-x: .25rem}.g-md-1,.gy-md-1{--bs-gutter-y: .25rem}.g-md-2,.gx-md-2{--bs-gutter-x: .5rem}.g-md-2,.gy-md-2{--bs-gutter-y: .5rem}.g-md-3,.gx-md-3{--bs-gutter-x: 1rem}.g-md-3,.gy-md-3{--bs-gutter-y: 1rem}.g-md-4,.gx-md-4{--bs-gutter-x: 1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y: 1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x: 3rem}.g-md-5,.gy-md-5{--bs-gutter-y: 3rem}}@media (min-width: 992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:6.25%}.col-lg-2{flex:0 0 auto;width:12.5%}.col-lg-3{flex:0 0 auto;width:18.75%}.col-lg-4{flex:0 0 auto;width:25%}.col-lg-5{flex:0 0 auto;width:31.25%}.col-lg-6{flex:0 0 auto;width:37.5%}.col-lg-7{flex:0 0 auto;width:43.75%}.col-lg-8{flex:0 0 auto;width:50%}.col-lg-9{flex:0 0 auto;width:56.25%}.col-lg-10{flex:0 0 auto;width:62.5%}.col-lg-11{flex:0 0 auto;width:68.75%}.col-lg-12{flex:0 0 auto;width:75%}.col-lg-13{flex:0 0 auto;width:81.25%}.col-lg-14{flex:0 0 auto;width:87.5%}.col-lg-15{flex:0 0 auto;width:93.75%}.col-lg-16{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:6.25%}.offset-lg-2{margin-left:12.5%}.offset-lg-3{margin-left:18.75%}.offset-lg-4{margin-left:25%}.offset-lg-5{margin-left:31.25%}.offset-lg-6{margin-left:37.5%}.offset-lg-7{margin-left:43.75%}.offset-lg-8{margin-left:50%}.offset-lg-9{margin-left:56.25%}.offset-lg-10{margin-left:62.5%}.offset-lg-11{margin-left:68.75%}.offset-lg-12{margin-left:75%}.offset-lg-13{margin-left:81.25%}.offset-lg-14{margin-left:87.5%}.offset-lg-15{margin-left:93.75%}.g-lg-0,.gx-lg-0{--bs-gutter-x: 0}.g-lg-0,.gy-lg-0{--bs-gutter-y: 0}.g-lg-1,.gx-lg-1{--bs-gutter-x: .25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y: .25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x: .5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y: .5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x: 1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y: 1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x: 1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y: 1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x: 3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y: 3rem}}@media (min-width: 1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:6.25%}.col-xl-2{flex:0 0 auto;width:12.5%}.col-xl-3{flex:0 0 auto;width:18.75%}.col-xl-4{flex:0 0 auto;width:25%}.col-xl-5{flex:0 0 auto;width:31.25%}.col-xl-6{flex:0 0 auto;width:37.5%}.col-xl-7{flex:0 0 auto;width:43.75%}.col-xl-8{flex:0 0 auto;width:50%}.col-xl-9{flex:0 0 auto;width:56.25%}.col-xl-10{flex:0 0 auto;width:62.5%}.col-xl-11{flex:0 0 auto;width:68.75%}.col-xl-12{flex:0 0 auto;width:75%}.col-xl-13{flex:0 0 auto;width:81.25%}.col-xl-14{flex:0 0 auto;width:87.5%}.col-xl-15{flex:0 0 auto;width:93.75%}.col-xl-16{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:6.25%}.offset-xl-2{margin-left:12.5%}.offset-xl-3{margin-left:18.75%}.offset-xl-4{margin-left:25%}.offset-xl-5{margin-left:31.25%}.offset-xl-6{margin-left:37.5%}.offset-xl-7{margin-left:43.75%}.offset-xl-8{margin-left:50%}.offset-xl-9{margin-left:56.25%}.offset-xl-10{margin-left:62.5%}.offset-xl-11{margin-left:68.75%}.offset-xl-12{margin-left:75%}.offset-xl-13{margin-left:81.25%}.offset-xl-14{margin-left:87.5%}.offset-xl-15{margin-left:93.75%}.g-xl-0,.gx-xl-0{--bs-gutter-x: 0}.g-xl-0,.gy-xl-0{--bs-gutter-y: 0}.g-xl-1,.gx-xl-1{--bs-gutter-x: .25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y: .25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x: .5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y: .5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x: 1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y: 1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x: 1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y: 1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x: 3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y: 3rem}}@media (min-width: 1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:6.25%}.col-xxl-2{flex:0 0 auto;width:12.5%}.col-xxl-3{flex:0 0 auto;width:18.75%}.col-xxl-4{flex:0 0 auto;width:25%}.col-xxl-5{flex:0 0 auto;width:31.25%}.col-xxl-6{flex:0 0 auto;width:37.5%}.col-xxl-7{flex:0 0 auto;width:43.75%}.col-xxl-8{flex:0 0 auto;width:50%}.col-xxl-9{flex:0 0 auto;width:56.25%}.col-xxl-10{flex:0 0 auto;width:62.5%}.col-xxl-11{flex:0 0 auto;width:68.75%}.col-xxl-12{flex:0 0 auto;width:75%}.col-xxl-13{flex:0 0 auto;width:81.25%}.col-xxl-14{flex:0 0 auto;width:87.5%}.col-xxl-15{flex:0 0 auto;width:93.75%}.col-xxl-16{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:6.25%}.offset-xxl-2{margin-left:12.5%}.offset-xxl-3{margin-left:18.75%}.offset-xxl-4{margin-left:25%}.offset-xxl-5{margin-left:31.25%}.offset-xxl-6{margin-left:37.5%}.offset-xxl-7{margin-left:43.75%}.offset-xxl-8{margin-left:50%}.offset-xxl-9{margin-left:56.25%}.offset-xxl-10{margin-left:62.5%}.offset-xxl-11{margin-left:68.75%}.offset-xxl-12{margin-left:75%}.offset-xxl-13{margin-left:81.25%}.offset-xxl-14{margin-left:87.5%}.offset-xxl-15{margin-left:93.75%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x: 0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y: 0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x: .25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y: .25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x: .5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y: .5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x: 1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y: 1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x: 1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y: 1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x: 3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y: 3rem}}.table,table{--bs-table-bg: rgba(0,0,0,0);--bs-table-striped-color: #1d2d35;--bs-table-striped-bg: rgba(29,45,53,.05);--bs-table-active-color: #1d2d35;--bs-table-active-bg: rgba(29,45,53,.1);--bs-table-hover-color: #1d2d35;--bs-table-hover-bg: rgba(29,45,53,.075);width:100%;margin-bottom:1rem;color:#1d2d35;vertical-align:top;border-color:#e9ecef}.table>:not(caption)>*>*,table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody,table>tbody{vertical-align:inherit}.table>thead,table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*,table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg: var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg: var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg: var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg: #dfd5e7;--bs-table-striped-bg: #d5cdde;--bs-table-striped-color: #1d2d35;--bs-table-active-bg: #ccc4d5;--bs-table-active-color: #1d2d35;--bs-table-hover-bg: #d0c8da;--bs-table-hover-color: #1d2d35;color:#1d2d35;border-color:#ccc4d5}.table-secondary{--bs-table-bg: #e2e3e5;--bs-table-striped-bg: #d8dadc;--bs-table-striped-color: #1d2d35;--bs-table-active-bg: #ced1d3;--bs-table-active-color: #1d2d35;--bs-table-hover-bg: #d3d5d8;--bs-table-hover-color: #1d2d35;color:#1d2d35;border-color:#ced1d3}.table-success{--bs-table-bg: #d1e7dd;--bs-table-striped-bg: #c8ded5;--bs-table-striped-color: #1d2d35;--bs-table-active-bg: #bfd4cc;--bs-table-active-color: #1d2d35;--bs-table-hover-bg: #c4d9d0;--bs-table-hover-color: #1d2d35;color:#1d2d35;border-color:#bfd4cc}.table-info{--bs-table-bg: #cff4fc;--bs-table-striped-bg: #c6eaf2;--bs-table-striped-color: #1d2d35;--bs-table-active-bg: #bde0e8;--bs-table-active-color: #1d2d35;--bs-table-hover-bg: #c2e5ed;--bs-table-hover-color: #1d2d35;color:#1d2d35;border-color:#bde0e8}.table-warning{--bs-table-bg: #fff9cc;--bs-table-striped-bg: #f4efc4;--bs-table-striped-color: #1d2d35;--bs-table-active-bg: #e8e5bd;--bs-table-active-color: #1d2d35;--bs-table-hover-bg: #eeeac1;--bs-table-hover-color: #1d2d35;color:#1d2d35;border-color:#e8e5bd}.table-danger{--bs-table-bg: #f8d7da;--bs-table-striped-bg: #edcfd2;--bs-table-striped-color: #1d2d35;--bs-table-active-bg: #e2c6ca;--bs-table-active-color: #1d2d35;--bs-table-hover-bg: #e8cace;--bs-table-hover-color: #1d2d35;color:#1d2d35;border-color:#e2c6ca}.table-light{--bs-table-bg: #f8f9fa;--bs-table-striped-bg: #edeff0;--bs-table-striped-color: #1d2d35;--bs-table-active-bg: #e2e5e6;--bs-table-active-color: #1d2d35;--bs-table-hover-bg: #e8eaeb;--bs-table-hover-color: #1d2d35;color:#1d2d35;border-color:#e2e5e6}.table-dark,body.dark table{--bs-table-bg: #212529;--bs-table-striped-bg: #2c3034;--bs-table-striped-color: #fff;--bs-table-active-bg: #373b3e;--bs-table-active-color: #fff;--bs-table-hover-bg: #323539;--bs-table-hover-color: #fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width: 575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width: 1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control,.search-form .search-field,.comment-form input[type=text],.comment-form input[type=email],.comment-form input[type=url],.comment-form textarea{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#1d2d35;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control,.search-form .search-field,.comment-form input[type=text],.comment-form input[type=email],.comment-form input[type=url],.comment-form textarea{transition:none}}.form-control[type=file],.search-form [type=file].search-field,.comment-form input[type=file][type=text],.comment-form input[type=file][type=email],.comment-form input[type=file][type=url],.comment-form textarea[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]),.search-form [type=file].search-field:not(:disabled):not([readonly]),.comment-form input[type=file][type=text]:not(:disabled):not([readonly]),.comment-form input[type=file][type=email]:not(:disabled):not([readonly]),.comment-form input[type=file][type=url]:not(:disabled):not([readonly]),.comment-form textarea[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus,.search-form .search-field:focus,.comment-form input[type=text]:focus,.comment-form input[type=email]:focus,.comment-form input[type=url]:focus,.comment-form textarea:focus{color:#1d2d35;background-color:#fff;border-color:#ae97c3;outline:0;box-shadow:0 0 0 .25rem rgba(93,47,134,.25)}.form-control::-webkit-date-and-time-value,.search-form .search-field::-webkit-date-and-time-value,.comment-form input[type=text]::-webkit-date-and-time-value,.comment-form input[type=email]::-webkit-date-and-time-value,.comment-form input[type=url]::-webkit-date-and-time-value,.comment-form textarea::-webkit-date-and-time-value{height:1.5em}.form-control::placeholder,.search-form .search-field::placeholder,.comment-form input[type=text]::placeholder,.comment-form input[type=email]::placeholder,.comment-form input[type=url]::placeholder,.comment-form textarea::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.search-form .search-field:disabled,.comment-form input[type=text]:disabled,.comment-form input[type=email]:disabled,.comment-form input[type=url]:disabled,.comment-form textarea:disabled,.form-control[readonly],.search-form [readonly].search-field,.comment-form input[readonly][type=text],.comment-form input[readonly][type=email],.comment-form input[readonly][type=url],.comment-form textarea[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button,.search-form .search-field::file-selector-button,.comment-form input[type=text]::file-selector-button,.comment-form input[type=email]::file-selector-button,.comment-form input[type=url]::file-selector-button,.comment-form textarea::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;margin-inline-end:.75rem;color:#1d2d35;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control::file-selector-button,.search-form .search-field::file-selector-button,.comment-form input[type=text]::file-selector-button,.comment-form input[type=email]::file-selector-button,.comment-form input[type=url]::file-selector-button,.comment-form textarea::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button,.search-form .search-field:hover:not(:disabled):not([readonly])::file-selector-button,.comment-form input[type=text]:hover:not(:disabled):not([readonly])::file-selector-button,.comment-form input[type=email]:hover:not(:disabled):not([readonly])::file-selector-button,.comment-form input[type=url]:hover:not(:disabled):not([readonly])::file-selector-button,.comment-form textarea:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button,.search-form .search-field::-webkit-file-upload-button,.comment-form input[type=text]::-webkit-file-upload-button,.comment-form input[type=email]::-webkit-file-upload-button,.comment-form input[type=url]::-webkit-file-upload-button,.comment-form textarea::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;margin-inline-end:.75rem;color:#1d2d35;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-control::-webkit-file-upload-button,.search-form .search-field::-webkit-file-upload-button,.comment-form input[type=text]::-webkit-file-upload-button,.comment-form input[type=email]::-webkit-file-upload-button,.comment-form input[type=url]::-webkit-file-upload-button,.comment-form textarea::-webkit-file-upload-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button,.search-form .search-field:hover:not(:disabled):not([readonly])::-webkit-file-upload-button,.comment-form input[type=text]:hover:not(:disabled):not([readonly])::-webkit-file-upload-button,.comment-form input[type=email]:hover:not(:disabled):not([readonly])::-webkit-file-upload-button,.comment-form input[type=url]:hover:not(:disabled):not([readonly])::-webkit-file-upload-button,.comment-form textarea:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#1d2d35;background-color:rgba(0,0,0,0);border:solid rgba(0,0,0,0);border-width:1px 0}.form-control-plaintext.form-control-sm,.form-control-plaintext.form-control-lg{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;margin-inline-end:1rem}textarea.form-control,.search-form textarea.search-field,.comment-form textarea{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#1d2d35;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;appearance:none}.form-select:focus{border-color:#ae97c3;outline:0;box-shadow:0 0 0 .25rem rgba(93,47,134,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:rgba(0,0,0,0);text-shadow:0 0 0 #1d2d35}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);appearance:none;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#ae97c3;outline:0;box-shadow:0 0 0 .25rem rgba(93,47,134,.25)}.form-check-input:checked{background-color:#5d2f86;border-color:#5d2f86}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#5d2f86;border-color:#5d2f86;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input[disabled]~.form-check-label,.form-check-input:disabled~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280,0,0,.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion: reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ae97c3'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.btn-check[disabled]+.btn,.search-form .btn-check[disabled]+.search-submit,.comment-form .btn-check[disabled]+input[type=submit],.btn-check:disabled+.btn,.search-form .btn-check:disabled+.search-submit,.comment-form .btn-check:disabled+input[type=submit]{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:rgba(0,0,0,0);appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(93,47,134,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(93,47,134,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#5d2f86;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.form-range::-webkit-slider-thumb{transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#cec1db}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#5d2f86;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion: reduce){.form-range::-moz-range-thumb{transition:none}}.form-range::-moz-range-thumb:active{background-color:#cec1db}.form-range::-moz-range-track{width:100%;height:.5rem;color:rgba(0,0,0,0);cursor:pointer;background-color:#dee2e6;border-color:rgba(0,0,0,0);border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.search-form .form-floating>.search-field,.comment-form .form-floating>input[type=text],.comment-form .form-floating>input[type=email],.comment-form .form-floating>input[type=url],.comment-form .form-floating>textarea,.form-floating>.form-select{height:calc(3.5rem + 2px);padding:1rem .75rem}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid rgba(0,0,0,0);transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion: reduce){.form-floating>label{transition:none}}.form-floating>.form-control::placeholder,.search-form .form-floating>.search-field::placeholder,.comment-form .form-floating>input[type=text]::placeholder,.comment-form .form-floating>input[type=email]::placeholder,.comment-form .form-floating>input[type=url]::placeholder,.comment-form .form-floating>textarea::placeholder{color:rgba(0,0,0,0)}.form-floating>.form-control:focus,.search-form .form-floating>.search-field:focus,.comment-form .form-floating>input[type=text]:focus,.comment-form .form-floating>input[type=email]:focus,.comment-form .form-floating>input[type=url]:focus,.comment-form .form-floating>textarea:focus,.form-floating>.form-control:not(:placeholder-shown),.search-form .form-floating>.search-field:not(:placeholder-shown),.comment-form .form-floating>input[type=text]:not(:placeholder-shown),.comment-form .form-floating>input[type=email]:not(:placeholder-shown),.comment-form .form-floating>input[type=url]:not(:placeholder-shown),.comment-form .form-floating>textarea:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill,.search-form .form-floating>.search-field:-webkit-autofill,.comment-form .form-floating>input[type=text]:-webkit-autofill,.comment-form .form-floating>input[type=email]:-webkit-autofill,.comment-form .form-floating>input[type=url]:-webkit-autofill,.comment-form .form-floating>textarea:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus~label,.search-form .form-floating>.search-field:focus~label,.comment-form .form-floating>input[type=text]:focus~label,.comment-form .form-floating>input[type=email]:focus~label,.comment-form .form-floating>input[type=url]:focus~label,.comment-form .form-floating>textarea:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.search-form .form-floating>.search-field:not(:placeholder-shown)~label,.comment-form .form-floating>input[type=text]:not(:placeholder-shown)~label,.comment-form .form-floating>input[type=email]:not(:placeholder-shown)~label,.comment-form .form-floating>input[type=url]:not(:placeholder-shown)~label,.comment-form .form-floating>textarea:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label,.search-form .form-floating>.search-field:-webkit-autofill~label,.comment-form .form-floating>input[type=text]:-webkit-autofill~label,.comment-form .form-floating>input[type=email]:-webkit-autofill~label,.comment-form .form-floating>input[type=url]:-webkit-autofill~label,.comment-form .form-floating>textarea:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.search-form .input-group>.search-field,.comment-form .input-group>input[type=text],.comment-form .input-group>input[type=email],.comment-form .input-group>input[type=url],.comment-form .input-group>textarea,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.search-form .input-group>.search-field:focus,.comment-form .input-group>input[type=text]:focus,.comment-form .input-group>input[type=email]:focus,.comment-form .input-group>input[type=url]:focus,.comment-form .input-group>textarea:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn,.input-group .search-form .search-submit,.search-form .input-group .search-submit,.input-group .comment-form input[type=submit],.comment-form .input-group input[type=submit]{position:relative;z-index:2}.input-group .btn:focus,.input-group .search-form .search-submit:focus,.search-form .input-group .search-submit:focus,.input-group .comment-form input[type=submit]:focus,.comment-form .input-group input[type=submit]:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#1d2d35;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.form-control,.search-form .input-group-lg>.search-field,.comment-form .input-group-lg>input[type=text],.comment-form .input-group-lg>input[type=email],.comment-form .input-group-lg>input[type=url],.comment-form .input-group-lg>textarea,.input-group-lg>.form-select,.input-group-lg>.input-group-text,.input-group-lg>.btn,.search-form .input-group-lg>.search-submit,.comment-form .input-group-lg>input[type=submit]{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.form-control,.search-form .input-group-sm>.search-field,.comment-form .input-group-sm>input[type=text],.comment-form .input-group-sm>input[type=email],.comment-form .input-group-sm>input[type=url],.comment-form .input-group-sm>textarea,.input-group-sm>.form-select,.input-group-sm>.input-group-text,.input-group-sm>.btn,.search-form .input-group-sm>.search-submit,.comment-form .input-group-sm>input[type=submit]{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu),.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip,.is-valid~.valid-feedback,.is-valid~.valid-tooltip{display:block}.was-validated .form-control:valid,.was-validated .search-form .search-field:valid,.search-form .was-validated .search-field:valid,.was-validated .comment-form input[type=text]:valid,.comment-form .was-validated input[type=text]:valid,.was-validated .comment-form input[type=email]:valid,.comment-form .was-validated input[type=email]:valid,.was-validated .comment-form input[type=url]:valid,.comment-form .was-validated input[type=url]:valid,.was-validated .comment-form textarea:valid,.comment-form .was-validated textarea:valid,.form-control.is-valid,.search-form .is-valid.search-field,.comment-form input.is-valid[type=text],.comment-form input.is-valid[type=email],.comment-form input.is-valid[type=url],.comment-form textarea.is-valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:valid:focus,.was-validated .search-form .search-field:valid:focus,.search-form .was-validated .search-field:valid:focus,.was-validated .comment-form input[type=text]:valid:focus,.comment-form .was-validated input[type=text]:valid:focus,.was-validated .comment-form input[type=email]:valid:focus,.comment-form .was-validated input[type=email]:valid:focus,.was-validated .comment-form input[type=url]:valid:focus,.comment-form .was-validated input[type=url]:valid:focus,.was-validated .comment-form textarea:valid:focus,.comment-form .was-validated textarea:valid:focus,.form-control.is-valid:focus,.search-form .is-valid.search-field:focus,.comment-form input.is-valid[type=text]:focus,.comment-form input.is-valid[type=email]:focus,.comment-form input.is-valid[type=url]:focus,.comment-form textarea.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,.was-validated .search-form textarea.search-field:valid,.search-form .was-validated textarea.search-field:valid,.was-validated .comment-form textarea:valid,.comment-form .was-validated textarea:valid,textarea.form-control.is-valid,.search-form textarea.is-valid.search-field,.comment-form textarea.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:valid,.form-select.is-valid{border-color:#198754;padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:valid:focus,.form-select.is-valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid,.form-check-input.is-valid{border-color:#198754}.was-validated .form-check-input:valid:checked,.form-check-input.is-valid:checked{background-color:#198754}.was-validated .form-check-input:valid:focus,.form-check-input.is-valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated .form-check-input:valid~.form-check-label,.form-check-input.is-valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.was-validated .input-group .form-control:valid,.was-validated .input-group .search-form .search-field:valid,.search-form .was-validated .input-group .search-field:valid,.was-validated .input-group .comment-form input[type=text]:valid,.comment-form .was-validated .input-group input[type=text]:valid,.was-validated .input-group .comment-form input[type=email]:valid,.comment-form .was-validated .input-group input[type=email]:valid,.was-validated .input-group .comment-form input[type=url]:valid,.comment-form .was-validated .input-group input[type=url]:valid,.was-validated .input-group .comment-form textarea:valid,.comment-form .was-validated .input-group textarea:valid,.input-group .form-control.is-valid,.input-group .search-form .is-valid.search-field,.search-form .input-group .is-valid.search-field,.input-group .comment-form input.is-valid[type=text],.comment-form .input-group input.is-valid[type=text],.input-group .comment-form input.is-valid[type=email],.comment-form .input-group input.is-valid[type=email],.input-group .comment-form input.is-valid[type=url],.comment-form .input-group input.is-valid[type=url],.input-group .comment-form textarea.is-valid,.comment-form .input-group textarea.is-valid,.was-validated .input-group .form-select:valid,.input-group .form-select.is-valid{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip,.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip{display:block}.was-validated .form-control:invalid,.was-validated .search-form .search-field:invalid,.search-form .was-validated .search-field:invalid,.was-validated .comment-form input[type=text]:invalid,.comment-form .was-validated input[type=text]:invalid,.was-validated .comment-form input[type=email]:invalid,.comment-form .was-validated input[type=email]:invalid,.was-validated .comment-form input[type=url]:invalid,.comment-form .was-validated input[type=url]:invalid,.was-validated .comment-form textarea:invalid,.comment-form .was-validated textarea:invalid,.form-control.is-invalid,.search-form .is-invalid.search-field,.comment-form input.is-invalid[type=text],.comment-form input.is-invalid[type=email],.comment-form input.is-invalid[type=url],.comment-form textarea.is-invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-control:invalid:focus,.was-validated .search-form .search-field:invalid:focus,.search-form .was-validated .search-field:invalid:focus,.was-validated .comment-form input[type=text]:invalid:focus,.comment-form .was-validated input[type=text]:invalid:focus,.was-validated .comment-form input[type=email]:invalid:focus,.comment-form .was-validated input[type=email]:invalid:focus,.was-validated .comment-form input[type=url]:invalid:focus,.comment-form .was-validated input[type=url]:invalid:focus,.was-validated .comment-form textarea:invalid:focus,.comment-form .was-validated textarea:invalid:focus,.form-control.is-invalid:focus,.search-form .is-invalid.search-field:focus,.comment-form input.is-invalid[type=text]:focus,.comment-form input.is-invalid[type=email]:focus,.comment-form input.is-invalid[type=url]:focus,.comment-form textarea.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,.was-validated .search-form textarea.search-field:invalid,.search-form .was-validated textarea.search-field:invalid,.was-validated .comment-form textarea:invalid,.comment-form .was-validated textarea:invalid,textarea.form-control.is-invalid,.search-form textarea.is-invalid.search-field,.comment-form textarea.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.was-validated .form-select:invalid,.form-select.is-invalid{border-color:#dc3545;padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.was-validated .form-select:invalid:focus,.form-select.is-invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid,.form-check-input.is-invalid{border-color:#dc3545}.was-validated .form-check-input:invalid:checked,.form-check-input.is-invalid:checked{background-color:#dc3545}.was-validated .form-check-input:invalid:focus,.form-check-input.is-invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated .form-check-input:invalid~.form-check-label,.form-check-input.is-invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.was-validated .input-group .form-control:invalid,.was-validated .input-group .search-form .search-field:invalid,.search-form .was-validated .input-group .search-field:invalid,.was-validated .input-group .comment-form input[type=text]:invalid,.comment-form .was-validated .input-group input[type=text]:invalid,.was-validated .input-group .comment-form input[type=email]:invalid,.comment-form .was-validated .input-group input[type=email]:invalid,.was-validated .input-group .comment-form input[type=url]:invalid,.comment-form .was-validated .input-group input[type=url]:invalid,.was-validated .input-group .comment-form textarea:invalid,.comment-form .was-validated .input-group textarea:invalid,.input-group .form-control.is-invalid,.input-group .search-form .is-invalid.search-field,.search-form .input-group .is-invalid.search-field,.input-group .comment-form input.is-invalid[type=text],.comment-form .input-group input.is-invalid[type=text],.input-group .comment-form input.is-invalid[type=email],.comment-form .input-group input.is-invalid[type=email],.input-group .comment-form input.is-invalid[type=url],.comment-form .input-group input.is-invalid[type=url],.input-group .comment-form textarea.is-invalid,.comment-form .input-group textarea.is-invalid,.was-validated .input-group .form-select:invalid,.input-group .form-select.is-invalid{z-index:3}.btn,.search-form .search-submit,.comment-form input[type=submit]{display:inline-block;font-weight:400;line-height:1.5;color:#1d2d35;text-align:center;vertical-align:middle;cursor:pointer;user-select:none;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.btn,.search-form .search-submit,.comment-form input[type=submit]{transition:none}}.btn:hover,.search-form .search-submit:hover,.comment-form input[type=submit]:hover{color:#1d2d35}.btn-check:focus+.btn,.search-form .btn-check:focus+.search-submit,.comment-form .btn-check:focus+input[type=submit],.btn:focus,.search-form .search-submit:focus,.comment-form input[type=submit]:focus{outline:0;box-shadow:0 0 0 .25rem rgba(93,47,134,.25)}.btn:disabled,.search-form .search-submit:disabled,.comment-form input[type=submit]:disabled,.btn.disabled,.search-form .disabled.search-submit,.comment-form input.disabled[type=submit],fieldset:disabled .btn,fieldset:disabled .search-form .search-submit,.search-form fieldset:disabled .search-submit,fieldset:disabled .comment-form input[type=submit],.comment-form fieldset:disabled input[type=submit]{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#5d2f86;border-color:#5d2f86}.btn-primary:hover{color:#fff;background-color:#4f2872;border-color:#4a266b}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#4f2872;border-color:#4a266b;box-shadow:0 0 0 .25rem rgba(117,78,152,.5)}.btn-check:checked+.btn-primary,.btn-check:active+.btn-primary,.btn-primary:active,.btn-primary.active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#4a266b;border-color:#462365}.btn-check:checked+.btn-primary:focus,.btn-check:active+.btn-primary:focus,.btn-primary:active:focus,.btn-primary.active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(117,78,152,.5)}.btn-primary:disabled,.btn-primary.disabled{color:#fff;background-color:#5d2f86;border-color:#5d2f86}.btn-secondary,.search-form .search-submit,.comment-form input[type=submit]{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover,.search-form .search-submit:hover,.comment-form input[type=submit]:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.search-form .btn-check:focus+.search-submit,.comment-form .btn-check:focus+input[type=submit],.btn-secondary:focus,.search-form .search-submit:focus,.comment-form input[type=submit]:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:checked+.btn-secondary,.search-form .btn-check:checked+.search-submit,.comment-form .btn-check:checked+input[type=submit],.btn-check:active+.btn-secondary,.search-form .btn-check:active+.search-submit,.comment-form .btn-check:active+input[type=submit],.btn-secondary:active,.search-form .search-submit:active,.comment-form input[type=submit]:active,.btn-secondary.active,.search-form .active.search-submit,.comment-form input.active[type=submit],.show>.btn-secondary.dropdown-toggle,.search-form .show>.dropdown-toggle.search-submit,.comment-form .show>input.dropdown-toggle[type=submit]{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:checked+.btn-secondary:focus,.search-form .btn-check:checked+.search-submit:focus,.comment-form .btn-check:checked+input[type=submit]:focus,.btn-check:active+.btn-secondary:focus,.search-form .btn-check:active+.search-submit:focus,.comment-form .btn-check:active+input[type=submit]:focus,.btn-secondary:active:focus,.search-form .search-submit:active:focus,.comment-form input[type=submit]:active:focus,.btn-secondary.active:focus,.search-form .active.search-submit:focus,.comment-form input.active[type=submit]:focus,.show>.btn-secondary.dropdown-toggle:focus,.search-form .show>.dropdown-toggle.search-submit:focus,.comment-form .show>input.dropdown-toggle[type=submit]:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary:disabled,.search-form .search-submit:disabled,.comment-form input[type=submit]:disabled,.btn-secondary.disabled,.search-form .disabled.search-submit,.comment-form input.disabled[type=submit]{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:checked+.btn-success,.btn-check:active+.btn-success,.btn-success:active,.btn-success.active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:checked+.btn-success:focus,.btn-check:active+.btn-success:focus,.btn-success:active:focus,.btn-success.active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success:disabled,.btn-success.disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#1d2d35;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#1d2d35;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#1d2d35;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(15,178,212,.5)}.btn-check:checked+.btn-info,.btn-check:active+.btn-info,.btn-info:active,.btn-info.active,.show>.btn-info.dropdown-toggle{color:#1d2d35;background-color:#3dd5f3;border-color:#25cff2}.btn-check:checked+.btn-info:focus,.btn-check:active+.btn-info:focus,.btn-info:active:focus,.btn-info.active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(15,178,212,.5)}.btn-info:disabled,.btn-info.disabled{color:#1d2d35;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#1d2d35;background-color:#ffe000;border-color:#ffe000}.btn-warning:hover{color:#1d2d35;background-color:#ffe526;border-color:#ffe319}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#1d2d35;background-color:#ffe526;border-color:#ffe319;box-shadow:0 0 0 .25rem rgba(221,197,8,.5)}.btn-check:checked+.btn-warning,.btn-check:active+.btn-warning,.btn-warning:active,.btn-warning.active,.show>.btn-warning.dropdown-toggle{color:#1d2d35;background-color:#ffe633;border-color:#ffe319}.btn-check:checked+.btn-warning:focus,.btn-check:active+.btn-warning:focus,.btn-warning:active:focus,.btn-warning.active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(221,197,8,.5)}.btn-warning:disabled,.btn-warning.disabled{color:#1d2d35;background-color:#ffe000;border-color:#ffe000}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:checked+.btn-danger,.btn-check:active+.btn-danger,.btn-danger:active,.btn-danger.active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:checked+.btn-danger:focus,.btn-check:active+.btn-danger:focus,.btn-danger:active:focus,.btn-danger.active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger:disabled,.btn-danger.disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#1d2d35;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#1d2d35;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#1d2d35;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(215,218,220,.5)}.btn-check:checked+.btn-light,.btn-check:active+.btn-light,.btn-light:active,.btn-light.active,.show>.btn-light.dropdown-toggle{color:#1d2d35;background-color:#f9fafb;border-color:#f9fafb}.btn-check:checked+.btn-light:focus,.btn-check:active+.btn-light:focus,.btn-light:active:focus,.btn-light.active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(215,218,220,.5)}.btn-light:disabled,.btn-light.disabled{color:#1d2d35;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:checked+.btn-dark,.btn-check:active+.btn-dark,.btn-dark:active,.btn-dark.active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:checked+.btn-dark:focus,.btn-check:active+.btn-dark:focus,.btn-dark:active:focus,.btn-dark.active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark:disabled,.btn-dark.disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#5d2f86;border-color:#5d2f86}.btn-outline-primary:hover{color:#fff;background-color:#5d2f86;border-color:#5d2f86}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(93,47,134,.5)}.btn-check:checked+.btn-outline-primary,.btn-check:active+.btn-outline-primary,.btn-outline-primary:active,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show{color:#fff;background-color:#5d2f86;border-color:#5d2f86}.btn-check:checked+.btn-outline-primary:focus,.btn-check:active+.btn-outline-primary:focus,.btn-outline-primary:active:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(93,47,134,.5)}.btn-outline-primary:disabled,.btn-outline-primary.disabled{color:#5d2f86;background-color:rgba(0,0,0,0)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:checked+.btn-outline-secondary,.btn-check:active+.btn-outline-secondary,.btn-outline-secondary:active,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:checked+.btn-outline-secondary:focus,.btn-check:active+.btn-outline-secondary:focus,.btn-outline-secondary:active:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary:disabled,.btn-outline-secondary.disabled{color:#6c757d;background-color:rgba(0,0,0,0)}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:checked+.btn-outline-success,.btn-check:active+.btn-outline-success,.btn-outline-success:active,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show{color:#fff;background-color:#198754;border-color:#198754}.btn-check:checked+.btn-outline-success:focus,.btn-check:active+.btn-outline-success:focus,.btn-outline-success:active:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success:disabled,.btn-outline-success.disabled{color:#198754;background-color:rgba(0,0,0,0)}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#1d2d35;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:checked+.btn-outline-info,.btn-check:active+.btn-outline-info,.btn-outline-info:active,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show{color:#1d2d35;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:checked+.btn-outline-info:focus,.btn-check:active+.btn-outline-info:focus,.btn-outline-info:active:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info:disabled,.btn-outline-info.disabled{color:#0dcaf0;background-color:rgba(0,0,0,0)}.btn-outline-warning{color:#ffe000;border-color:#ffe000}.btn-outline-warning:hover{color:#1d2d35;background-color:#ffe000;border-color:#ffe000}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,224,0,.5)}.btn-check:checked+.btn-outline-warning,.btn-check:active+.btn-outline-warning,.btn-outline-warning:active,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show{color:#1d2d35;background-color:#ffe000;border-color:#ffe000}.btn-check:checked+.btn-outline-warning:focus,.btn-check:active+.btn-outline-warning:focus,.btn-outline-warning:active:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(255,224,0,.5)}.btn-outline-warning:disabled,.btn-outline-warning.disabled{color:#ffe000;background-color:rgba(0,0,0,0)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:checked+.btn-outline-danger,.btn-check:active+.btn-outline-danger,.btn-outline-danger:active,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:checked+.btn-outline-danger:focus,.btn-check:active+.btn-outline-danger:focus,.btn-outline-danger:active:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger:disabled,.btn-outline-danger.disabled{color:#dc3545;background-color:rgba(0,0,0,0)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#1d2d35;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:checked+.btn-outline-light,.btn-check:active+.btn-outline-light,.btn-outline-light:active,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show{color:#1d2d35;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:checked+.btn-outline-light:focus,.btn-check:active+.btn-outline-light:focus,.btn-outline-light:active:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light:disabled,.btn-outline-light.disabled{color:#f8f9fa;background-color:rgba(0,0,0,0)}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:checked+.btn-outline-dark,.btn-check:active+.btn-outline-dark,.btn-outline-dark:active,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show{color:#fff;background-color:#212529;border-color:#212529}.btn-check:checked+.btn-outline-dark:focus,.btn-check:active+.btn-outline-dark:focus,.btn-outline-dark:active:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark:disabled,.btn-outline-dark.disabled{color:#212529;background-color:rgba(0,0,0,0)}.btn-link{font-weight:400;color:#5d2f86;text-decoration:none}.btn-link:hover{color:#4a266b}.btn-link:disabled,.btn-link.disabled{color:#6c757d}.btn-lg,.btn-group-lg>.btn,.search-form .btn-group-lg>.search-submit,.comment-form .btn-group-lg>input[type=submit]{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-sm,.btn-group-sm>.btn,.search-form .btn-group-sm>.search-submit,.comment-form .btn-group-sm>input[type=submit]{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion: reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion: reduce){.collapsing{transition:none}}.dropup,.dropend,.dropdown,.dropstart{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid rgba(0,0,0,0);border-bottom:0;border-left:.3em solid rgba(0,0,0,0)}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#1d2d35;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(29,45,53,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position: start}.dropdown-menu-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-end{--bs-position: end}.dropdown-menu-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}@media (min-width: 576px){.dropdown-menu-sm-start{--bs-position: start}.dropdown-menu-sm-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-sm-end{--bs-position: end}.dropdown-menu-sm-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 768px){.dropdown-menu-md-start{--bs-position: start}.dropdown-menu-md-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-md-end{--bs-position: end}.dropdown-menu-md-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 992px){.dropdown-menu-lg-start{--bs-position: start}.dropdown-menu-lg-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-lg-end{--bs-position: end}.dropdown-menu-lg-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 1200px){.dropdown-menu-xl-start{--bs-position: start}.dropdown-menu-xl-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-xl-end{--bs-position: end}.dropdown-menu-xl-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}@media (min-width: 1400px){.dropdown-menu-xxl-start{--bs-position: start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto /* rtl:ignore */;left:0 /* rtl:ignore */}.dropdown-menu-xxl-end{--bs-position: end}.dropdown-menu-xxl-end[data-bs-popper]{right:0 /* rtl:ignore */;left:auto /* rtl:ignore */}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid rgba(0,0,0,0);border-bottom:.3em solid;border-left:.3em solid rgba(0,0,0,0)}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu{top:0;right:auto;left:100%}.dropend .dropdown-menu[data-bs-popper]{margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:0;border-bottom:.3em solid rgba(0,0,0,0);border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu{top:0;right:100%;left:auto}.dropstart .dropdown-menu[data-bs-popper]{margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid rgba(0,0,0,0);border-right:.3em solid;border-bottom:.3em solid rgba(0,0,0,0)}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(29,45,53,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:rgba(0,0,0,0);border:0}.dropdown-item:hover,.dropdown-item:focus{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#5d2f86}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:rgba(0,0,0,0)}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(29,45,53,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:hover,.dropdown-menu-dark .dropdown-item:focus{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#5d2f86}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(29,45,53,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group>.btn,.search-form .btn-group>.search-submit,.comment-form .btn-group>input[type=submit],.btn-group-vertical>.btn,.search-form .btn-group-vertical>.search-submit,.comment-form .btn-group-vertical>input[type=submit]{position:relative;flex:1 1 auto}.btn-group>.btn-check:checked+.btn,.search-form .btn-group>.btn-check:checked+.search-submit,.comment-form .btn-group>.btn-check:checked+input[type=submit],.btn-group>.btn-check:focus+.btn,.search-form .btn-group>.btn-check:focus+.search-submit,.comment-form .btn-group>.btn-check:focus+input[type=submit],.btn-group>.btn:hover,.search-form .btn-group>.search-submit:hover,.comment-form .btn-group>input[type=submit]:hover,.btn-group>.btn:focus,.search-form .btn-group>.search-submit:focus,.comment-form .btn-group>input[type=submit]:focus,.btn-group>.btn:active,.search-form .btn-group>.search-submit:active,.comment-form .btn-group>input[type=submit]:active,.btn-group>.btn.active,.search-form .btn-group>.active.search-submit,.comment-form .btn-group>input.active[type=submit],.btn-group-vertical>.btn-check:checked+.btn,.search-form .btn-group-vertical>.btn-check:checked+.search-submit,.comment-form .btn-group-vertical>.btn-check:checked+input[type=submit],.btn-group-vertical>.btn-check:focus+.btn,.search-form .btn-group-vertical>.btn-check:focus+.search-submit,.comment-form .btn-group-vertical>.btn-check:focus+input[type=submit],.btn-group-vertical>.btn:hover,.search-form .btn-group-vertical>.search-submit:hover,.comment-form .btn-group-vertical>input[type=submit]:hover,.btn-group-vertical>.btn:focus,.search-form .btn-group-vertical>.search-submit:focus,.comment-form .btn-group-vertical>input[type=submit]:focus,.btn-group-vertical>.btn:active,.search-form .btn-group-vertical>.search-submit:active,.comment-form .btn-group-vertical>input[type=submit]:active,.btn-group-vertical>.btn.active,.search-form .btn-group-vertical>.active.search-submit,.comment-form .btn-group-vertical>input.active[type=submit]{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn:not(:first-child),.search-form .btn-group>.search-submit:not(:first-child),.comment-form .btn-group>input[type=submit]:not(:first-child),.btn-group>.btn-group:not(:first-child){margin-left:-1px}.btn-group>.btn:not(:last-child):not(.dropdown-toggle),.search-form .btn-group>.search-submit:not(:last-child):not(.dropdown-toggle),.comment-form .btn-group>input[type=submit]:not(:last-child):not(.dropdown-toggle),.btn-group>.btn-group:not(:last-child)>.btn,.search-form .btn-group>.btn-group:not(:last-child)>.search-submit,.comment-form .btn-group>.btn-group:not(:last-child)>input[type=submit]{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:nth-child(n+3),.search-form .btn-group>.search-submit:nth-child(n+3),.comment-form .btn-group>input[type=submit]:nth-child(n+3),.btn-group>:not(.btn-check)+.btn,.search-form .btn-group>:not(.btn-check)+.search-submit,.comment-form .btn-group>:not(.btn-check)+input[type=submit],.btn-group>.btn-group:not(:first-child)>.btn,.search-form .btn-group>.btn-group:not(:first-child)>.search-submit,.comment-form .btn-group>.btn-group:not(:first-child)>input[type=submit]{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-sm+.dropdown-toggle-split,.btn-group-sm>.btn+.dropdown-toggle-split,.search-form .btn-group-sm>.search-submit+.dropdown-toggle-split,.comment-form .btn-group-sm>input[type=submit]+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-lg+.dropdown-toggle-split,.btn-group-lg>.btn+.dropdown-toggle-split,.search-form .btn-group-lg>.search-submit+.dropdown-toggle-split,.comment-form .btn-group-lg>input[type=submit]+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.search-form .btn-group-vertical>.search-submit,.comment-form .btn-group-vertical>input[type=submit],.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn:not(:first-child),.search-form .btn-group-vertical>.search-submit:not(:first-child),.comment-form .btn-group-vertical>input[type=submit]:not(:first-child),.btn-group-vertical>.btn-group:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle),.search-form .btn-group-vertical>.search-submit:not(:last-child):not(.dropdown-toggle),.comment-form .btn-group-vertical>input[type=submit]:not(:last-child):not(.dropdown-toggle),.btn-group-vertical>.btn-group:not(:last-child)>.btn,.search-form .btn-group-vertical>.btn-group:not(:last-child)>.search-submit,.comment-form .btn-group-vertical>.btn-group:not(:last-child)>input[type=submit]{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn~.btn,.search-form .btn-group-vertical>.search-submit~.btn,.search-form .btn-group-vertical>.btn~.search-submit,.search-form .btn-group-vertical>.search-submit~.search-submit,.comment-form .btn-group-vertical>input[type=submit]~.btn,.comment-form .search-form .btn-group-vertical>input[type=submit]~.search-submit,.search-form .comment-form .btn-group-vertical>input[type=submit]~.search-submit,.comment-form .btn-group-vertical>.btn~input[type=submit],.comment-form .search-form .btn-group-vertical>.search-submit~input[type=submit],.search-form .comment-form .btn-group-vertical>.search-submit~input[type=submit],.comment-form .btn-group-vertical>input[type=submit]~input[type=submit],.btn-group-vertical>.btn-group:not(:first-child)>.btn,.search-form .btn-group-vertical>.btn-group:not(:first-child)>.search-submit,.comment-form .btn-group-vertical>.btn-group:not(:first-child)>input[type=submit]{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link,.banner .nav a{display:block;padding:.5rem 1rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion: reduce){.nav-link,.banner .nav a{transition:none}}.nav-link.disabled,.banner .nav a.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link,.nav-tabs .banner .nav a,.banner .nav .nav-tabs a{margin-bottom:-1px;background:none;border:1px solid rgba(0,0,0,0);border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:hover,.nav-tabs .banner .nav a:hover,.banner .nav .nav-tabs a:hover,.nav-tabs .nav-link:focus,.nav-tabs .banner .nav a:focus,.banner .nav .nav-tabs a:focus{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled,.nav-tabs .banner .nav a.disabled,.banner .nav .nav-tabs a.disabled{color:#6c757d;background-color:rgba(0,0,0,0);border-color:rgba(0,0,0,0)}.nav-tabs .nav-link.active,.nav-tabs .banner .nav a.active,.banner .nav .nav-tabs a.active,.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-item.show .banner .nav a,.banner .nav .nav-tabs .nav-item.show a,.nav-tabs .banner .nav li.show .nav-link,.nav-tabs .banner .nav li.show a,.banner .nav .nav-tabs li.show .nav-link,.banner .nav .nav-tabs li.show a{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link,.nav-pills .banner .nav a,.banner .nav .nav-pills a{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .banner .nav a.active,.banner .nav .nav-pills a.active,.nav-pills .show>.nav-link,.nav-pills .banner .nav .show>a,.banner .nav .nav-pills .show>a{color:#fff;background-color:#5d2f86}.nav-fill>.nav-link,.banner .nav .nav-fill>a,.nav-fill .nav-item,.nav-fill .banner .nav li,.banner .nav .nav-fill li{flex:1 1 auto;text-align:center}.nav-justified>.nav-link,.banner .nav .nav-justified>a,.nav-justified .nav-item,.nav-justified .banner .nav li,.banner .nav .nav-justified li{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-fill .nav-item .banner .nav a,.banner .nav .nav-fill .nav-item a,.nav-fill .banner .nav li .nav-link,.nav-fill .banner .nav li a,.banner .nav .nav-fill li .nav-link,.banner .nav .nav-fill li a,.nav-justified .nav-item .nav-link,.nav-justified .nav-item .banner .nav a,.banner .nav .nav-justified .nav-item a,.nav-justified .banner .nav li .nav-link,.nav-justified .banner .nav li a,.banner .nav .nav-justified li .nav-link,.banner .nav .nav-justified li a{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-sm,.navbar>.container-md,.navbar>.container-lg,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link,.navbar-nav .banner .nav a,.banner .nav .navbar-nav a{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:rgba(0,0,0,0);border:1px solid rgba(0,0,0,0);border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height, 75vh);overflow-y:auto}@media (min-width: 576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link,.navbar-expand-sm .navbar-nav .banner .nav a,.banner .nav .navbar-expand-sm .navbar-nav a{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (min-width: 768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link,.navbar-expand-md .navbar-nav .banner .nav a,.banner .nav .navbar-expand-md .navbar-nav a{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (min-width: 992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link,.navbar-expand-lg .navbar-nav .banner .nav a,.banner .nav .navbar-expand-lg .navbar-nav a{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (min-width: 1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link,.navbar-expand-xl .navbar-nav .banner .nav a,.banner .nav .navbar-expand-xl .navbar-nav a{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}@media (min-width: 1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link,.navbar-expand-xxl .navbar-nav .banner .nav a,.banner .nav .navbar-expand-xxl .navbar-nav a{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link,.navbar-expand .navbar-nav .banner .nav a,.banner .nav .navbar-expand .navbar-nav a{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:#5d2f86}.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:focus{color:#5d2f86}.navbar-light .navbar-nav .nav-link,.navbar-light .navbar-nav .banner .nav a,.banner .nav .navbar-light .navbar-nav a{color:#1d2d35}.navbar-light .navbar-nav .nav-link:hover,.navbar-light .navbar-nav .banner .nav a:hover,.banner .nav .navbar-light .navbar-nav a:hover,.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .banner .nav a:focus,.banner .nav .navbar-light .navbar-nav a:focus{color:#5d2f86}.navbar-light .navbar-nav .nav-link.disabled,.navbar-light .navbar-nav .banner .nav a.disabled,.banner .nav .navbar-light .navbar-nav a.disabled{color:rgba(29,45,53,.3)}.navbar-light .navbar-nav .show>.nav-link,.navbar-light .navbar-nav .banner .nav .show>a,.banner .nav .navbar-light .navbar-nav .show>a,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .banner .nav a.active,.banner .nav .navbar-light .navbar-nav a.active{color:#5d2f86}.navbar-light .navbar-toggler{color:#1d2d35;border-color:rgba(29,45,53,.1)}.navbar-light .navbar-toggler-icon{background-image:none}.navbar-light .navbar-text{color:#1d2d35}.navbar-light .navbar-text a,.navbar-light .navbar-text a:hover,.navbar-light .navbar-text a:focus{color:#5d2f86}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:hover,.navbar-dark .navbar-brand:focus{color:#fff}.navbar-dark .navbar-nav .nav-link,.navbar-dark .navbar-nav .banner .nav a,.banner .nav .navbar-dark .navbar-nav a{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:hover,.navbar-dark .navbar-nav .banner .nav a:hover,.banner .nav .navbar-dark .navbar-nav a:hover,.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .banner .nav a:focus,.banner .nav .navbar-dark .navbar-nav a:focus{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled,.navbar-dark .navbar-nav .banner .nav a.disabled,.banner .nav .navbar-dark .navbar-nav a.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .show>.nav-link,.navbar-dark .navbar-nav .banner .nav .show>a,.banner .nav .navbar-dark .navbar-nav .show>a,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .banner .nav a.active,.banner .nav .navbar-dark .navbar-nav a.active{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:none}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:hover,.navbar-dark .navbar-text a:focus{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid #e9ecef;border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(29,45,53,.03);border-bottom:1px solid #e9ecef}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(29,45,53,.03);border-top:1px solid #e9ecef}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-top,.card-img-bottom{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:24px}@media (min-width: 576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-img-top,.card-group>.card:not(:last-child) .card-header{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-img-bottom,.card-group>.card:not(:last-child) .card-footer{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-img-top,.card-group>.card:not(:first-child) .card-header{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-img-bottom,.card-group>.card:not(:first-child) .card-footer{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#1d2d35;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion: reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#542a79;background-color:#efeaf3;box-shadow:inset 0 -1px 0 rgba(29,45,53,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23542a79'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%231d2d35'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion: reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#ae97c3;outline:0;box-shadow:0 0 0 .25rem rgba(93,47,134,.25)}.accordion-header{margin-bottom:0}.accordion-item{margin-bottom:-1px;background-color:#fff;border:1px solid rgba(29,45,53,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:last-of-type{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#5d2f86;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion: reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#4a266b;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#4a266b;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(93,47,134,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#5d2f86;border-color:#5d2f86}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge,.search-form .search-submit .badge,.comment-form input[type=submit] .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1.5rem;margin-bottom:0;border:0 solid rgba(0,0,0,0);border-radius:0}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4.5rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1.5rem}.alert-primary{color:#fff;background-color:#5d2f86;border-color:#5d2f86}.alert-primary .alert-link{color:#ccc}.alert-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.alert-secondary .alert-link{color:#ccc}.alert-success{color:#fff;background-color:#198754;border-color:#198754}.alert-success .alert-link{color:#ccc}.alert-info{color:#1d2d35;background-color:#0dcaf0;border-color:#0dcaf0}.alert-info .alert-link{color:#17242a}.alert-warning{color:#1d2d35;background-color:#ffe000;border-color:#ffe000}.alert-warning .alert-link{color:#17242a}.alert-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.alert-danger .alert-link{color:#ccc}.alert-light{color:#1d2d35;background-color:#f8f9fa;border-color:#f8f9fa}.alert-light .alert-link{color:#17242a}.alert-dark{color:#fff;background-color:#212529;border-color:#212529}.alert-dark .alert-link{color:#ccc}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#5d2f86;transition:width .6s ease}@media (prefers-reduced-motion: reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg, rgba(255,255,255,.15) 25%, rgba(0,0,0,0) 25%, rgba(0,0,0,0) 50%, rgba(255,255,255,.15) 50%, rgba(255,255,255,.15) 75%, rgba(0,0,0,0) 75%, rgba(0,0,0,0));background-size:1rem 1rem}.progress-bar-animated{animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion: reduce){.progress-bar-animated{animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:hover,.list-group-item-action:focus{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#1d2d35;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;background-color:#fff;border:1px solid rgba(29,45,53,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#5d2f86;border-color:#5d2f86}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width: 576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width: 1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#381c50;background-color:#dfd5e7}.list-group-item-primary.list-group-item-action:hover,.list-group-item-primary.list-group-item-action:focus{color:#381c50;background-color:#c9c0d0}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#381c50;border-color:#381c50}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:hover,.list-group-item-secondary.list-group-item-action:focus{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:hover,.list-group-item-success.list-group-item-action:focus{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#1d2d35;background-color:#cff4fc}.list-group-item-info.list-group-item-action:hover,.list-group-item-info.list-group-item-action:focus{color:#1d2d35;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#1d2d35;border-color:#1d2d35}.list-group-item-warning{color:#1d2d35;background-color:#fff9cc}.list-group-item-warning.list-group-item-action:hover,.list-group-item-warning.list-group-item-action:focus{color:#1d2d35;background-color:#e6e0b8}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#1d2d35;border-color:#1d2d35}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:hover,.list-group-item-danger.list-group-item-action:focus{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#1d2d35;background-color:#fefefe}.list-group-item-light.list-group-item-action:hover,.list-group-item-light.list-group-item-action:focus{color:#1d2d35;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#1d2d35;border-color:#1d2d35}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:hover,.list-group-item-dark.list-group-item-action:focus{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#1d2d35;background:rgba(0,0,0,0) url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%231d2d35'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#1d2d35;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(93,47,134,.25);opacity:1}.btn-close:disabled,.btn-close.disabled{pointer-events:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(29,45,53,.15);border-radius:.25rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:24px}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0, -50px)}@media (prefers-reduced-motion: reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(29,45,53,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#1d2d35}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #e9ecef;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #e9ecef;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width: 576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width: 992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width: 1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width: 575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width: 767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width: 991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width: 1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width: 1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:"Jost",-apple-system,blinkmacsystemfont,"Segoe UI",roboto,"Helvetica Neue",arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-tooltip-top,.bs-tooltip-auto[data-popper-placement^=top]{padding:.4rem 0}.bs-tooltip-top .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow{bottom:0}.bs-tooltip-top .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#1d2d35}.bs-tooltip-end,.bs-tooltip-auto[data-popper-placement^=right]{padding:0 .4rem}.bs-tooltip-end .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-end .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#1d2d35}.bs-tooltip-bottom,.bs-tooltip-auto[data-popper-placement^=bottom]{padding:.4rem 0}.bs-tooltip-bottom .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow{top:0}.bs-tooltip-bottom .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#1d2d35}.bs-tooltip-start,.bs-tooltip-auto[data-popper-placement^=left]{padding:0 .4rem}.bs-tooltip-start .tooltip-arrow,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-start .tooltip-arrow::before,.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#1d2d35}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#1d2d35;border-radius:.25rem}.popover{position:absolute;top:0;left:0 /* rtl:ignore */;z-index:1070;display:block;max-width:276px;font-family:"Jost",-apple-system,blinkmacsystemfont,"Segoe UI",roboto,"Helvetica Neue",arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(29,45,53,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::before,.popover .popover-arrow::after{position:absolute;display:block;content:"";border-color:rgba(0,0,0,0);border-style:solid}.bs-popover-top>.popover-arrow,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-top>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(29,45,53,.25)}.bs-popover-top>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-end>.popover-arrow,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-end>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(29,45,53,.25)}.bs-popover-end>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-bottom>.popover-arrow,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-bottom>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(29,45,53,.25)}.bs-popover-bottom>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-bottom .popover-header::before,.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-start>.popover-arrow,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-start>.popover-arrow::before,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(29,45,53,.25)}.bs-popover-start>.popover-arrow::after,.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid #d8d8d8;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#1d2d35}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion: reduce){.carousel-item{transition:none}}.carousel-item.active,.carousel-item-next,.carousel-item-prev{display:block}.carousel-item-next:not(.carousel-item-start),.active.carousel-item-end{transform:translateX(100%)}.carousel-item-prev:not(.carousel-item-end),.active.carousel-item-start{transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item.active,.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end{z-index:1;opacity:1}.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion: reduce){.carousel-fade .active.carousel-item-start,.carousel-fade .active.carousel-item-end{transition:none}}.carousel-control-prev,.carousel-control-next{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:none;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion: reduce){.carousel-control-prev,.carousel-control-next{transition:none}}.carousel-control-prev:hover,.carousel-control-prev:focus,.carousel-control-next:hover,.carousel-control-next:focus{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-prev-icon,.carousel-control-next-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid rgba(0,0,0,0);border-bottom:10px solid rgba(0,0,0,0);opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion: reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-prev-icon,.carousel-dark .carousel-control-next-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#1d2d35}.carousel-dark .carousel-caption{color:#1d2d35}@keyframes spinner-border{to{transform:rotate(360deg) /* rtl:ignore */}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:rgba(0,0,0,0);border-radius:50%;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion: reduce){.spinner-border,.spinner-grow{animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1040;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion: reduce){.offcanvas{transition:none}}.offcanvas-header{display:flex;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(29,45,53,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(29,45,53,.2);transform:translateX(100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(29,45,53,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.offcanvas-backdrop::before{position:fixed;top:0;left:0;z-index:1039;width:100vw;height:100vh;content:"";background-color:rgba(29,45,53,.5)}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#5d2f86}.link-primary:hover,.link-primary:focus{color:#4a266b}.link-secondary{color:#6c757d}.link-secondary:hover,.link-secondary:focus{color:#565e64}.link-success{color:#198754}.link-success:hover,.link-success:focus{color:#146c43}.link-info{color:#0dcaf0}.link-info:hover,.link-info:focus{color:#3dd5f3}.link-warning{color:#ffe000}.link-warning:hover,.link-warning:focus{color:#ffe633}.link-danger{color:#dc3545}.link-danger:hover,.link-danger:focus{color:#b02a37}.link-light{color:#f8f9fa}.link-light:hover,.link-light:focus{color:#f9fafb}.link-dark{color:#212529}.link-dark:hover,.link-dark:focus{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio: 100%}.ratio-4x3{--bs-aspect-ratio: 75%}.ratio-16x9{--bs-aspect-ratio: 56.25%}.ratio-21x9{--bs-aspect-ratio: 42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:sticky;top:0;z-index:1020}@media (min-width: 576px){.sticky-sm-top{position:sticky;top:0;z-index:1020}}@media (min-width: 768px){.sticky-md-top{position:sticky;top:0;z-index:1020}}@media (min-width: 992px){.sticky-lg-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1200px){.sticky-xl-top{position:sticky;top:0;z-index:1020}}@media (min-width: 1400px){.sticky-xxl-top{position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute !important;width:1px !important;height:1px !important;padding:0 !important;margin:-1px !important;overflow:hidden !important;clip:rect(0, 0, 0, 0) !important;white-space:nowrap !important;border:0 !important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.align-baseline{vertical-align:baseline !important}.align-top{vertical-align:top !important}.align-middle{vertical-align:middle !important}.align-bottom{vertical-align:bottom !important}.align-text-bottom{vertical-align:text-bottom !important}.align-text-top{vertical-align:text-top !important}.float-start{float:left !important}.float-end{float:right !important}.float-none{float:none !important}.overflow-auto{overflow:auto !important}.overflow-hidden{overflow:hidden !important}.overflow-visible{overflow:visible !important}.overflow-scroll{overflow:scroll !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.d-block{display:block !important}.d-grid{display:grid !important}.d-table{display:table !important}.d-table-row{display:table-row !important}.d-table-cell{display:table-cell !important}.d-flex{display:flex !important}.d-inline-flex{display:inline-flex !important}.d-none{display:none !important}.shadow{box-shadow:0 .5rem 1rem rgba(29,45,53,.15) !important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(29,45,53,.075) !important}.shadow-lg{box-shadow:0 1rem 3rem rgba(29,45,53,.175) !important}.shadow-none{box-shadow:none !important}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.position-sticky{position:sticky !important}.top-0{top:0 !important}.top-50{top:50% !important}.top-100{top:100% !important}.bottom-0{bottom:0 !important}.bottom-50{bottom:50% !important}.bottom-100{bottom:100% !important}.start-0{left:0 !important}.start-50{left:50% !important}.start-100{left:100% !important}.end-0{right:0 !important}.end-50{right:50% !important}.end-100{right:100% !important}.translate-middle{transform:translate(-50%, -50%) !important}.translate-middle-x{transform:translateX(-50%) !important}.translate-middle-y{transform:translateY(-50%) !important}.border{border:1px solid #e9ecef !important}.border-0{border:0 !important}.border-top{border-top:1px solid #e9ecef !important}.border-top-0{border-top:0 !important}.border-end{border-right:1px solid #e9ecef !important}.border-end-0{border-right:0 !important}.border-bottom{border-bottom:1px solid #e9ecef !important}.border-bottom-0{border-bottom:0 !important}.border-start{border-left:1px solid #e9ecef !important}.border-start-0{border-left:0 !important}.border-primary{border-color:#5d2f86 !important}.border-secondary{border-color:#6c757d !important}.border-success{border-color:#198754 !important}.border-info{border-color:#0dcaf0 !important}.border-warning{border-color:#ffe000 !important}.border-danger{border-color:#dc3545 !important}.border-light{border-color:#f8f9fa !important}.border-dark{border-color:#212529 !important}.border-white{border-color:#fff !important}.border-1{border-width:1px !important}.border-2{border-width:2px !important}.border-3{border-width:3px !important}.border-4{border-width:4px !important}.border-5{border-width:5px !important}.w-25{width:25% !important}.w-50{width:50% !important}.w-75{width:75% !important}.w-100{width:100% !important}.w-auto{width:auto !important}.mw-100{max-width:100% !important}.vw-100{width:100vw !important}.min-vw-100{min-width:100vw !important}.h-25{height:25% !important}.h-50{height:50% !important}.h-75{height:75% !important}.h-100{height:100% !important}.h-auto{height:auto !important}.mh-100{max-height:100% !important}.vh-100{height:100vh !important}.min-vh-100{min-height:100vh !important}.flex-fill{flex:1 1 auto !important}.flex-row{flex-direction:row !important}.flex-column{flex-direction:column !important}.flex-row-reverse{flex-direction:row-reverse !important}.flex-column-reverse{flex-direction:column-reverse !important}.flex-grow-0{flex-grow:0 !important}.flex-grow-1{flex-grow:1 !important}.flex-shrink-0{flex-shrink:0 !important}.flex-shrink-1{flex-shrink:1 !important}.flex-wrap{flex-wrap:wrap !important}.flex-nowrap{flex-wrap:nowrap !important}.flex-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-0{gap:0 !important}.gap-1{gap:.25rem !important}.gap-2{gap:.5rem !important}.gap-3{gap:1rem !important}.gap-4{gap:1.5rem !important}.gap-5{gap:3rem !important}.justify-content-start{justify-content:flex-start !important}.justify-content-end{justify-content:flex-end !important}.justify-content-center{justify-content:center !important}.justify-content-between{justify-content:space-between !important}.justify-content-around{justify-content:space-around !important}.justify-content-evenly{justify-content:space-evenly !important}.align-items-start{align-items:flex-start !important}.align-items-end{align-items:flex-end !important}.align-items-center{align-items:center !important}.align-items-baseline{align-items:baseline !important}.align-items-stretch{align-items:stretch !important}.align-content-start{align-content:flex-start !important}.align-content-end{align-content:flex-end !important}.align-content-center{align-content:center !important}.align-content-between{align-content:space-between !important}.align-content-around{align-content:space-around !important}.align-content-stretch{align-content:stretch !important}.align-self-auto{align-self:auto !important}.align-self-start{align-self:flex-start !important}.align-self-end{align-self:flex-end !important}.align-self-center{align-self:center !important}.align-self-baseline{align-self:baseline !important}.align-self-stretch{align-self:stretch !important}.order-first{order:-1 !important}.order-0{order:0 !important}.order-1{order:1 !important}.order-2{order:2 !important}.order-3{order:3 !important}.order-4{order:4 !important}.order-5{order:5 !important}.order-last{order:6 !important}.m-0{margin:0 !important}.m-1{margin:.25rem !important}.m-2{margin:.5rem !important}.m-3{margin:1rem !important}.m-4{margin:1.5rem !important}.m-5{margin:3rem !important}.m-auto{margin:auto !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.mx-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-3{margin-right:1rem !important;margin-left:1rem !important}.mx-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-5{margin-right:3rem !important;margin-left:3rem !important}.mx-auto{margin-right:auto !important;margin-left:auto !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.my-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-0{margin-top:0 !important}.mt-1{margin-top:.25rem !important}.mt-2{margin-top:.5rem !important}.mt-3{margin-top:1rem !important}.mt-4{margin-top:1.5rem !important}.mt-5{margin-top:3rem !important}.mt-auto{margin-top:auto !important}.me-0{margin-right:0 !important}.me-1{margin-right:.25rem !important}.me-2{margin-right:.5rem !important}.me-3{margin-right:1rem !important}.me-4{margin-right:1.5rem !important}.me-5{margin-right:3rem !important}.me-auto{margin-right:auto !important}.mb-0{margin-bottom:0 !important}.mb-1{margin-bottom:.25rem !important}.mb-2{margin-bottom:.5rem !important}.mb-3{margin-bottom:1rem !important}.mb-4{margin-bottom:1.5rem !important}.mb-5{margin-bottom:3rem !important}.mb-auto{margin-bottom:auto !important}.ms-0{margin-left:0 !important}.ms-1{margin-left:.25rem !important}.ms-2{margin-left:.5rem !important}.ms-3{margin-left:1rem !important}.ms-4{margin-left:1.5rem !important}.ms-5{margin-left:3rem !important}.ms-auto{margin-left:auto !important}.m-n1{margin:-.25rem !important}.m-n2{margin:-.5rem !important}.m-n3{margin:-1rem !important}.m-n4{margin:-1.5rem !important}.m-n5{margin:-3rem !important}.mx-n1{margin-right:-.25rem !important;margin-left:-.25rem !important}.mx-n2{margin-right:-.5rem !important;margin-left:-.5rem !important}.mx-n3{margin-right:-1rem !important;margin-left:-1rem !important}.mx-n4{margin-right:-1.5rem !important;margin-left:-1.5rem !important}.mx-n5{margin-right:-3rem !important;margin-left:-3rem !important}.my-n1{margin-top:-.25rem !important;margin-bottom:-.25rem !important}.my-n2{margin-top:-.5rem !important;margin-bottom:-.5rem !important}.my-n3{margin-top:-1rem !important;margin-bottom:-1rem !important}.my-n4{margin-top:-1.5rem !important;margin-bottom:-1.5rem !important}.my-n5{margin-top:-3rem !important;margin-bottom:-3rem !important}.mt-n1{margin-top:-.25rem !important}.mt-n2{margin-top:-.5rem !important}.mt-n3{margin-top:-1rem !important}.mt-n4{margin-top:-1.5rem !important}.mt-n5{margin-top:-3rem !important}.me-n1{margin-right:-.25rem !important}.me-n2{margin-right:-.5rem !important}.me-n3{margin-right:-1rem !important}.me-n4{margin-right:-1.5rem !important}.me-n5{margin-right:-3rem !important}.mb-n1{margin-bottom:-.25rem !important}.mb-n2{margin-bottom:-.5rem !important}.mb-n3{margin-bottom:-1rem !important}.mb-n4{margin-bottom:-1.5rem !important}.mb-n5{margin-bottom:-3rem !important}.ms-n1{margin-left:-.25rem !important}.ms-n2{margin-left:-.5rem !important}.ms-n3{margin-left:-1rem !important}.ms-n4{margin-left:-1.5rem !important}.ms-n5{margin-left:-3rem !important}.p-0{padding:0 !important}.p-1{padding:.25rem !important}.p-2{padding:.5rem !important}.p-3{padding:1rem !important}.p-4{padding:1.5rem !important}.p-5{padding:3rem !important}.px-0{padding-right:0 !important;padding-left:0 !important}.px-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-3{padding-right:1rem !important;padding-left:1rem !important}.px-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-5{padding-right:3rem !important;padding-left:3rem !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.py-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-0{padding-top:0 !important}.pt-1{padding-top:.25rem !important}.pt-2{padding-top:.5rem !important}.pt-3{padding-top:1rem !important}.pt-4{padding-top:1.5rem !important}.pt-5{padding-top:3rem !important}.pe-0{padding-right:0 !important}.pe-1{padding-right:.25rem !important}.pe-2{padding-right:.5rem !important}.pe-3{padding-right:1rem !important}.pe-4{padding-right:1.5rem !important}.pe-5{padding-right:3rem !important}.pb-0{padding-bottom:0 !important}.pb-1{padding-bottom:.25rem !important}.pb-2{padding-bottom:.5rem !important}.pb-3{padding-bottom:1rem !important}.pb-4{padding-bottom:1.5rem !important}.pb-5{padding-bottom:3rem !important}.ps-0{padding-left:0 !important}.ps-1{padding-left:.25rem !important}.ps-2{padding-left:.5rem !important}.ps-3{padding-left:1rem !important}.ps-4{padding-left:1.5rem !important}.ps-5{padding-left:3rem !important}.font-monospace{font-family:var(--bs-font-monospace) !important}.fs-1{font-size:calc(1.375rem + 1.5vw) !important}.fs-2{font-size:calc(1.325rem + .9vw) !important}.fs-3{font-size:calc(1.3rem + .6vw) !important}.fs-4{font-size:calc(1.275rem + .3vw) !important}.fs-5{font-size:1.25rem !important}.fs-6{font-size:1rem !important}.fst-italic{font-style:italic !important}.fst-normal{font-style:normal !important}.fw-light{font-weight:300 !important}.fw-lighter{font-weight:lighter !important}.fw-normal{font-weight:400 !important}.fw-bold{font-weight:700 !important}.fw-bolder{font-weight:bolder !important}.lh-1{line-height:1 !important}.lh-sm{line-height:1.25 !important}.lh-base{line-height:1.5 !important}.lh-lg{line-height:2 !important}.text-start{text-align:left !important}.text-end{text-align:right !important}.text-center{text-align:center !important}.text-decoration-none{text-decoration:none !important}.text-decoration-underline{text-decoration:underline !important}.text-decoration-line-through{text-decoration:line-through !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-wrap{white-space:normal !important}.text-nowrap{white-space:nowrap !important}.text-break{word-wrap:break-word !important;word-break:break-word !important}.text-primary{color:#5d2f86 !important}.text-secondary{color:#6c757d !important}.text-success{color:#198754 !important}.text-info{color:#0dcaf0 !important}.text-warning{color:#ffe000 !important}.text-danger{color:#dc3545 !important}.text-light{color:#f8f9fa !important}.text-dark{color:#212529 !important}.text-white{color:#fff !important}.text-body{color:#1d2d35 !important}.text-muted{color:#6c757d !important}.text-black-50{color:rgba(29,45,53,.5) !important}.text-white-50{color:rgba(255,255,255,.5) !important}.text-reset{color:inherit !important}.bg-primary{background-color:#5d2f86 !important}.bg-secondary{background-color:#6c757d !important}.bg-success{background-color:#198754 !important}.bg-info{background-color:#0dcaf0 !important}.bg-warning{background-color:#ffe000 !important}.bg-danger{background-color:#dc3545 !important}.bg-light{background-color:#f8f9fa !important}.bg-dark{background-color:#212529 !important}.bg-body{background-color:#fff !important}.bg-white{background-color:#fff !important}.bg-transparent{background-color:rgba(0,0,0,0) !important}.bg-gradient{background-image:var(--bs-gradient) !important}.user-select-all{user-select:all !important}.user-select-auto{user-select:auto !important}.user-select-none{user-select:none !important}.pe-none{pointer-events:none !important}.pe-auto{pointer-events:auto !important}.rounded{border-radius:.25rem !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:.2rem !important}.rounded-2{border-radius:.25rem !important}.rounded-3{border-radius:.3rem !important}.rounded-circle{border-radius:50% !important}.rounded-pill{border-radius:50rem !important}.rounded-top{border-top-left-radius:.25rem !important;border-top-right-radius:.25rem !important}.rounded-end{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}.rounded-bottom{border-bottom-right-radius:.25rem !important;border-bottom-left-radius:.25rem !important}.rounded-start{border-bottom-left-radius:.25rem !important;border-top-left-radius:.25rem !important}.visible{visibility:visible !important}.invisible{visibility:hidden !important}@media (min-width: 576px){.float-sm-start{float:left !important}.float-sm-end{float:right !important}.float-sm-none{float:none !important}.d-sm-inline{display:inline !important}.d-sm-inline-block{display:inline-block !important}.d-sm-block{display:block !important}.d-sm-grid{display:grid !important}.d-sm-table{display:table !important}.d-sm-table-row{display:table-row !important}.d-sm-table-cell{display:table-cell !important}.d-sm-flex{display:flex !important}.d-sm-inline-flex{display:inline-flex !important}.d-sm-none{display:none !important}.flex-sm-fill{flex:1 1 auto !important}.flex-sm-row{flex-direction:row !important}.flex-sm-column{flex-direction:column !important}.flex-sm-row-reverse{flex-direction:row-reverse !important}.flex-sm-column-reverse{flex-direction:column-reverse !important}.flex-sm-grow-0{flex-grow:0 !important}.flex-sm-grow-1{flex-grow:1 !important}.flex-sm-shrink-0{flex-shrink:0 !important}.flex-sm-shrink-1{flex-shrink:1 !important}.flex-sm-wrap{flex-wrap:wrap !important}.flex-sm-nowrap{flex-wrap:nowrap !important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-sm-0{gap:0 !important}.gap-sm-1{gap:.25rem !important}.gap-sm-2{gap:.5rem !important}.gap-sm-3{gap:1rem !important}.gap-sm-4{gap:1.5rem !important}.gap-sm-5{gap:3rem !important}.justify-content-sm-start{justify-content:flex-start !important}.justify-content-sm-end{justify-content:flex-end !important}.justify-content-sm-center{justify-content:center !important}.justify-content-sm-between{justify-content:space-between !important}.justify-content-sm-around{justify-content:space-around !important}.justify-content-sm-evenly{justify-content:space-evenly !important}.align-items-sm-start{align-items:flex-start !important}.align-items-sm-end{align-items:flex-end !important}.align-items-sm-center{align-items:center !important}.align-items-sm-baseline{align-items:baseline !important}.align-items-sm-stretch{align-items:stretch !important}.align-content-sm-start{align-content:flex-start !important}.align-content-sm-end{align-content:flex-end !important}.align-content-sm-center{align-content:center !important}.align-content-sm-between{align-content:space-between !important}.align-content-sm-around{align-content:space-around !important}.align-content-sm-stretch{align-content:stretch !important}.align-self-sm-auto{align-self:auto !important}.align-self-sm-start{align-self:flex-start !important}.align-self-sm-end{align-self:flex-end !important}.align-self-sm-center{align-self:center !important}.align-self-sm-baseline{align-self:baseline !important}.align-self-sm-stretch{align-self:stretch !important}.order-sm-first{order:-1 !important}.order-sm-0{order:0 !important}.order-sm-1{order:1 !important}.order-sm-2{order:2 !important}.order-sm-3{order:3 !important}.order-sm-4{order:4 !important}.order-sm-5{order:5 !important}.order-sm-last{order:6 !important}.m-sm-0{margin:0 !important}.m-sm-1{margin:.25rem !important}.m-sm-2{margin:.5rem !important}.m-sm-3{margin:1rem !important}.m-sm-4{margin:1.5rem !important}.m-sm-5{margin:3rem !important}.m-sm-auto{margin:auto !important}.mx-sm-0{margin-right:0 !important;margin-left:0 !important}.mx-sm-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-sm-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-sm-3{margin-right:1rem !important;margin-left:1rem !important}.mx-sm-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-sm-5{margin-right:3rem !important;margin-left:3rem !important}.mx-sm-auto{margin-right:auto !important;margin-left:auto !important}.my-sm-0{margin-top:0 !important;margin-bottom:0 !important}.my-sm-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-sm-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-sm-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-sm-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-sm-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-sm-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-sm-0{margin-top:0 !important}.mt-sm-1{margin-top:.25rem !important}.mt-sm-2{margin-top:.5rem !important}.mt-sm-3{margin-top:1rem !important}.mt-sm-4{margin-top:1.5rem !important}.mt-sm-5{margin-top:3rem !important}.mt-sm-auto{margin-top:auto !important}.me-sm-0{margin-right:0 !important}.me-sm-1{margin-right:.25rem !important}.me-sm-2{margin-right:.5rem !important}.me-sm-3{margin-right:1rem !important}.me-sm-4{margin-right:1.5rem !important}.me-sm-5{margin-right:3rem !important}.me-sm-auto{margin-right:auto !important}.mb-sm-0{margin-bottom:0 !important}.mb-sm-1{margin-bottom:.25rem !important}.mb-sm-2{margin-bottom:.5rem !important}.mb-sm-3{margin-bottom:1rem !important}.mb-sm-4{margin-bottom:1.5rem !important}.mb-sm-5{margin-bottom:3rem !important}.mb-sm-auto{margin-bottom:auto !important}.ms-sm-0{margin-left:0 !important}.ms-sm-1{margin-left:.25rem !important}.ms-sm-2{margin-left:.5rem !important}.ms-sm-3{margin-left:1rem !important}.ms-sm-4{margin-left:1.5rem !important}.ms-sm-5{margin-left:3rem !important}.ms-sm-auto{margin-left:auto !important}.m-sm-n1{margin:-.25rem !important}.m-sm-n2{margin:-.5rem !important}.m-sm-n3{margin:-1rem !important}.m-sm-n4{margin:-1.5rem !important}.m-sm-n5{margin:-3rem !important}.mx-sm-n1{margin-right:-.25rem !important;margin-left:-.25rem !important}.mx-sm-n2{margin-right:-.5rem !important;margin-left:-.5rem !important}.mx-sm-n3{margin-right:-1rem !important;margin-left:-1rem !important}.mx-sm-n4{margin-right:-1.5rem !important;margin-left:-1.5rem !important}.mx-sm-n5{margin-right:-3rem !important;margin-left:-3rem !important}.my-sm-n1{margin-top:-.25rem !important;margin-bottom:-.25rem !important}.my-sm-n2{margin-top:-.5rem !important;margin-bottom:-.5rem !important}.my-sm-n3{margin-top:-1rem !important;margin-bottom:-1rem !important}.my-sm-n4{margin-top:-1.5rem !important;margin-bottom:-1.5rem !important}.my-sm-n5{margin-top:-3rem !important;margin-bottom:-3rem !important}.mt-sm-n1{margin-top:-.25rem !important}.mt-sm-n2{margin-top:-.5rem !important}.mt-sm-n3{margin-top:-1rem !important}.mt-sm-n4{margin-top:-1.5rem !important}.mt-sm-n5{margin-top:-3rem !important}.me-sm-n1{margin-right:-.25rem !important}.me-sm-n2{margin-right:-.5rem !important}.me-sm-n3{margin-right:-1rem !important}.me-sm-n4{margin-right:-1.5rem !important}.me-sm-n5{margin-right:-3rem !important}.mb-sm-n1{margin-bottom:-.25rem !important}.mb-sm-n2{margin-bottom:-.5rem !important}.mb-sm-n3{margin-bottom:-1rem !important}.mb-sm-n4{margin-bottom:-1.5rem !important}.mb-sm-n5{margin-bottom:-3rem !important}.ms-sm-n1{margin-left:-.25rem !important}.ms-sm-n2{margin-left:-.5rem !important}.ms-sm-n3{margin-left:-1rem !important}.ms-sm-n4{margin-left:-1.5rem !important}.ms-sm-n5{margin-left:-3rem !important}.p-sm-0{padding:0 !important}.p-sm-1{padding:.25rem !important}.p-sm-2{padding:.5rem !important}.p-sm-3{padding:1rem !important}.p-sm-4{padding:1.5rem !important}.p-sm-5{padding:3rem !important}.px-sm-0{padding-right:0 !important;padding-left:0 !important}.px-sm-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-sm-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-sm-3{padding-right:1rem !important;padding-left:1rem !important}.px-sm-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-sm-5{padding-right:3rem !important;padding-left:3rem !important}.py-sm-0{padding-top:0 !important;padding-bottom:0 !important}.py-sm-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-sm-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-sm-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-sm-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-sm-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-sm-0{padding-top:0 !important}.pt-sm-1{padding-top:.25rem !important}.pt-sm-2{padding-top:.5rem !important}.pt-sm-3{padding-top:1rem !important}.pt-sm-4{padding-top:1.5rem !important}.pt-sm-5{padding-top:3rem !important}.pe-sm-0{padding-right:0 !important}.pe-sm-1{padding-right:.25rem !important}.pe-sm-2{padding-right:.5rem !important}.pe-sm-3{padding-right:1rem !important}.pe-sm-4{padding-right:1.5rem !important}.pe-sm-5{padding-right:3rem !important}.pb-sm-0{padding-bottom:0 !important}.pb-sm-1{padding-bottom:.25rem !important}.pb-sm-2{padding-bottom:.5rem !important}.pb-sm-3{padding-bottom:1rem !important}.pb-sm-4{padding-bottom:1.5rem !important}.pb-sm-5{padding-bottom:3rem !important}.ps-sm-0{padding-left:0 !important}.ps-sm-1{padding-left:.25rem !important}.ps-sm-2{padding-left:.5rem !important}.ps-sm-3{padding-left:1rem !important}.ps-sm-4{padding-left:1.5rem !important}.ps-sm-5{padding-left:3rem !important}.text-sm-start{text-align:left !important}.text-sm-end{text-align:right !important}.text-sm-center{text-align:center !important}}@media (min-width: 768px){.float-md-start{float:left !important}.float-md-end{float:right !important}.float-md-none{float:none !important}.d-md-inline{display:inline !important}.d-md-inline-block{display:inline-block !important}.d-md-block{display:block !important}.d-md-grid{display:grid !important}.d-md-table{display:table !important}.d-md-table-row{display:table-row !important}.d-md-table-cell{display:table-cell !important}.d-md-flex{display:flex !important}.d-md-inline-flex{display:inline-flex !important}.d-md-none{display:none !important}.flex-md-fill{flex:1 1 auto !important}.flex-md-row{flex-direction:row !important}.flex-md-column{flex-direction:column !important}.flex-md-row-reverse{flex-direction:row-reverse !important}.flex-md-column-reverse{flex-direction:column-reverse !important}.flex-md-grow-0{flex-grow:0 !important}.flex-md-grow-1{flex-grow:1 !important}.flex-md-shrink-0{flex-shrink:0 !important}.flex-md-shrink-1{flex-shrink:1 !important}.flex-md-wrap{flex-wrap:wrap !important}.flex-md-nowrap{flex-wrap:nowrap !important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-md-0{gap:0 !important}.gap-md-1{gap:.25rem !important}.gap-md-2{gap:.5rem !important}.gap-md-3{gap:1rem !important}.gap-md-4{gap:1.5rem !important}.gap-md-5{gap:3rem !important}.justify-content-md-start{justify-content:flex-start !important}.justify-content-md-end{justify-content:flex-end !important}.justify-content-md-center{justify-content:center !important}.justify-content-md-between{justify-content:space-between !important}.justify-content-md-around{justify-content:space-around !important}.justify-content-md-evenly{justify-content:space-evenly !important}.align-items-md-start{align-items:flex-start !important}.align-items-md-end{align-items:flex-end !important}.align-items-md-center{align-items:center !important}.align-items-md-baseline{align-items:baseline !important}.align-items-md-stretch{align-items:stretch !important}.align-content-md-start{align-content:flex-start !important}.align-content-md-end{align-content:flex-end !important}.align-content-md-center{align-content:center !important}.align-content-md-between{align-content:space-between !important}.align-content-md-around{align-content:space-around !important}.align-content-md-stretch{align-content:stretch !important}.align-self-md-auto{align-self:auto !important}.align-self-md-start{align-self:flex-start !important}.align-self-md-end{align-self:flex-end !important}.align-self-md-center{align-self:center !important}.align-self-md-baseline{align-self:baseline !important}.align-self-md-stretch{align-self:stretch !important}.order-md-first{order:-1 !important}.order-md-0{order:0 !important}.order-md-1{order:1 !important}.order-md-2{order:2 !important}.order-md-3{order:3 !important}.order-md-4{order:4 !important}.order-md-5{order:5 !important}.order-md-last{order:6 !important}.m-md-0{margin:0 !important}.m-md-1{margin:.25rem !important}.m-md-2{margin:.5rem !important}.m-md-3{margin:1rem !important}.m-md-4{margin:1.5rem !important}.m-md-5{margin:3rem !important}.m-md-auto{margin:auto !important}.mx-md-0{margin-right:0 !important;margin-left:0 !important}.mx-md-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-md-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-md-3{margin-right:1rem !important;margin-left:1rem !important}.mx-md-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-md-5{margin-right:3rem !important;margin-left:3rem !important}.mx-md-auto{margin-right:auto !important;margin-left:auto !important}.my-md-0{margin-top:0 !important;margin-bottom:0 !important}.my-md-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-md-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-md-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-md-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-md-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-md-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-md-0{margin-top:0 !important}.mt-md-1{margin-top:.25rem !important}.mt-md-2{margin-top:.5rem !important}.mt-md-3{margin-top:1rem !important}.mt-md-4{margin-top:1.5rem !important}.mt-md-5{margin-top:3rem !important}.mt-md-auto{margin-top:auto !important}.me-md-0{margin-right:0 !important}.me-md-1{margin-right:.25rem !important}.me-md-2{margin-right:.5rem !important}.me-md-3{margin-right:1rem !important}.me-md-4{margin-right:1.5rem !important}.me-md-5{margin-right:3rem !important}.me-md-auto{margin-right:auto !important}.mb-md-0{margin-bottom:0 !important}.mb-md-1{margin-bottom:.25rem !important}.mb-md-2{margin-bottom:.5rem !important}.mb-md-3{margin-bottom:1rem !important}.mb-md-4{margin-bottom:1.5rem !important}.mb-md-5{margin-bottom:3rem !important}.mb-md-auto{margin-bottom:auto !important}.ms-md-0{margin-left:0 !important}.ms-md-1{margin-left:.25rem !important}.ms-md-2{margin-left:.5rem !important}.ms-md-3{margin-left:1rem !important}.ms-md-4{margin-left:1.5rem !important}.ms-md-5{margin-left:3rem !important}.ms-md-auto{margin-left:auto !important}.m-md-n1{margin:-.25rem !important}.m-md-n2{margin:-.5rem !important}.m-md-n3{margin:-1rem !important}.m-md-n4{margin:-1.5rem !important}.m-md-n5{margin:-3rem !important}.mx-md-n1{margin-right:-.25rem !important;margin-left:-.25rem !important}.mx-md-n2{margin-right:-.5rem !important;margin-left:-.5rem !important}.mx-md-n3{margin-right:-1rem !important;margin-left:-1rem !important}.mx-md-n4{margin-right:-1.5rem !important;margin-left:-1.5rem !important}.mx-md-n5{margin-right:-3rem !important;margin-left:-3rem !important}.my-md-n1{margin-top:-.25rem !important;margin-bottom:-.25rem !important}.my-md-n2{margin-top:-.5rem !important;margin-bottom:-.5rem !important}.my-md-n3{margin-top:-1rem !important;margin-bottom:-1rem !important}.my-md-n4{margin-top:-1.5rem !important;margin-bottom:-1.5rem !important}.my-md-n5{margin-top:-3rem !important;margin-bottom:-3rem !important}.mt-md-n1{margin-top:-.25rem !important}.mt-md-n2{margin-top:-.5rem !important}.mt-md-n3{margin-top:-1rem !important}.mt-md-n4{margin-top:-1.5rem !important}.mt-md-n5{margin-top:-3rem !important}.me-md-n1{margin-right:-.25rem !important}.me-md-n2{margin-right:-.5rem !important}.me-md-n3{margin-right:-1rem !important}.me-md-n4{margin-right:-1.5rem !important}.me-md-n5{margin-right:-3rem !important}.mb-md-n1{margin-bottom:-.25rem !important}.mb-md-n2{margin-bottom:-.5rem !important}.mb-md-n3{margin-bottom:-1rem !important}.mb-md-n4{margin-bottom:-1.5rem !important}.mb-md-n5{margin-bottom:-3rem !important}.ms-md-n1{margin-left:-.25rem !important}.ms-md-n2{margin-left:-.5rem !important}.ms-md-n3{margin-left:-1rem !important}.ms-md-n4{margin-left:-1.5rem !important}.ms-md-n5{margin-left:-3rem !important}.p-md-0{padding:0 !important}.p-md-1{padding:.25rem !important}.p-md-2{padding:.5rem !important}.p-md-3{padding:1rem !important}.p-md-4{padding:1.5rem !important}.p-md-5{padding:3rem !important}.px-md-0{padding-right:0 !important;padding-left:0 !important}.px-md-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-md-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-md-3{padding-right:1rem !important;padding-left:1rem !important}.px-md-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-md-5{padding-right:3rem !important;padding-left:3rem !important}.py-md-0{padding-top:0 !important;padding-bottom:0 !important}.py-md-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-md-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-md-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-md-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-md-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-md-0{padding-top:0 !important}.pt-md-1{padding-top:.25rem !important}.pt-md-2{padding-top:.5rem !important}.pt-md-3{padding-top:1rem !important}.pt-md-4{padding-top:1.5rem !important}.pt-md-5{padding-top:3rem !important}.pe-md-0{padding-right:0 !important}.pe-md-1{padding-right:.25rem !important}.pe-md-2{padding-right:.5rem !important}.pe-md-3{padding-right:1rem !important}.pe-md-4{padding-right:1.5rem !important}.pe-md-5{padding-right:3rem !important}.pb-md-0{padding-bottom:0 !important}.pb-md-1{padding-bottom:.25rem !important}.pb-md-2{padding-bottom:.5rem !important}.pb-md-3{padding-bottom:1rem !important}.pb-md-4{padding-bottom:1.5rem !important}.pb-md-5{padding-bottom:3rem !important}.ps-md-0{padding-left:0 !important}.ps-md-1{padding-left:.25rem !important}.ps-md-2{padding-left:.5rem !important}.ps-md-3{padding-left:1rem !important}.ps-md-4{padding-left:1.5rem !important}.ps-md-5{padding-left:3rem !important}.text-md-start{text-align:left !important}.text-md-end{text-align:right !important}.text-md-center{text-align:center !important}}@media (min-width: 992px){.float-lg-start{float:left !important}.float-lg-end{float:right !important}.float-lg-none{float:none !important}.d-lg-inline{display:inline !important}.d-lg-inline-block{display:inline-block !important}.d-lg-block{display:block !important}.d-lg-grid{display:grid !important}.d-lg-table{display:table !important}.d-lg-table-row{display:table-row !important}.d-lg-table-cell{display:table-cell !important}.d-lg-flex{display:flex !important}.d-lg-inline-flex{display:inline-flex !important}.d-lg-none{display:none !important}.flex-lg-fill{flex:1 1 auto !important}.flex-lg-row{flex-direction:row !important}.flex-lg-column{flex-direction:column !important}.flex-lg-row-reverse{flex-direction:row-reverse !important}.flex-lg-column-reverse{flex-direction:column-reverse !important}.flex-lg-grow-0{flex-grow:0 !important}.flex-lg-grow-1{flex-grow:1 !important}.flex-lg-shrink-0{flex-shrink:0 !important}.flex-lg-shrink-1{flex-shrink:1 !important}.flex-lg-wrap{flex-wrap:wrap !important}.flex-lg-nowrap{flex-wrap:nowrap !important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-lg-0{gap:0 !important}.gap-lg-1{gap:.25rem !important}.gap-lg-2{gap:.5rem !important}.gap-lg-3{gap:1rem !important}.gap-lg-4{gap:1.5rem !important}.gap-lg-5{gap:3rem !important}.justify-content-lg-start{justify-content:flex-start !important}.justify-content-lg-end{justify-content:flex-end !important}.justify-content-lg-center{justify-content:center !important}.justify-content-lg-between{justify-content:space-between !important}.justify-content-lg-around{justify-content:space-around !important}.justify-content-lg-evenly{justify-content:space-evenly !important}.align-items-lg-start{align-items:flex-start !important}.align-items-lg-end{align-items:flex-end !important}.align-items-lg-center{align-items:center !important}.align-items-lg-baseline{align-items:baseline !important}.align-items-lg-stretch{align-items:stretch !important}.align-content-lg-start{align-content:flex-start !important}.align-content-lg-end{align-content:flex-end !important}.align-content-lg-center{align-content:center !important}.align-content-lg-between{align-content:space-between !important}.align-content-lg-around{align-content:space-around !important}.align-content-lg-stretch{align-content:stretch !important}.align-self-lg-auto{align-self:auto !important}.align-self-lg-start{align-self:flex-start !important}.align-self-lg-end{align-self:flex-end !important}.align-self-lg-center{align-self:center !important}.align-self-lg-baseline{align-self:baseline !important}.align-self-lg-stretch{align-self:stretch !important}.order-lg-first{order:-1 !important}.order-lg-0{order:0 !important}.order-lg-1{order:1 !important}.order-lg-2{order:2 !important}.order-lg-3{order:3 !important}.order-lg-4{order:4 !important}.order-lg-5{order:5 !important}.order-lg-last{order:6 !important}.m-lg-0{margin:0 !important}.m-lg-1{margin:.25rem !important}.m-lg-2{margin:.5rem !important}.m-lg-3{margin:1rem !important}.m-lg-4{margin:1.5rem !important}.m-lg-5{margin:3rem !important}.m-lg-auto{margin:auto !important}.mx-lg-0{margin-right:0 !important;margin-left:0 !important}.mx-lg-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-lg-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-lg-3{margin-right:1rem !important;margin-left:1rem !important}.mx-lg-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-lg-5{margin-right:3rem !important;margin-left:3rem !important}.mx-lg-auto{margin-right:auto !important;margin-left:auto !important}.my-lg-0{margin-top:0 !important;margin-bottom:0 !important}.my-lg-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-lg-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-lg-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-lg-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-lg-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-lg-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-lg-0{margin-top:0 !important}.mt-lg-1{margin-top:.25rem !important}.mt-lg-2{margin-top:.5rem !important}.mt-lg-3{margin-top:1rem !important}.mt-lg-4{margin-top:1.5rem !important}.mt-lg-5{margin-top:3rem !important}.mt-lg-auto{margin-top:auto !important}.me-lg-0{margin-right:0 !important}.me-lg-1{margin-right:.25rem !important}.me-lg-2{margin-right:.5rem !important}.me-lg-3{margin-right:1rem !important}.me-lg-4{margin-right:1.5rem !important}.me-lg-5{margin-right:3rem !important}.me-lg-auto{margin-right:auto !important}.mb-lg-0{margin-bottom:0 !important}.mb-lg-1{margin-bottom:.25rem !important}.mb-lg-2{margin-bottom:.5rem !important}.mb-lg-3{margin-bottom:1rem !important}.mb-lg-4{margin-bottom:1.5rem !important}.mb-lg-5{margin-bottom:3rem !important}.mb-lg-auto{margin-bottom:auto !important}.ms-lg-0{margin-left:0 !important}.ms-lg-1{margin-left:.25rem !important}.ms-lg-2{margin-left:.5rem !important}.ms-lg-3{margin-left:1rem !important}.ms-lg-4{margin-left:1.5rem !important}.ms-lg-5{margin-left:3rem !important}.ms-lg-auto{margin-left:auto !important}.m-lg-n1{margin:-.25rem !important}.m-lg-n2{margin:-.5rem !important}.m-lg-n3{margin:-1rem !important}.m-lg-n4{margin:-1.5rem !important}.m-lg-n5{margin:-3rem !important}.mx-lg-n1{margin-right:-.25rem !important;margin-left:-.25rem !important}.mx-lg-n2{margin-right:-.5rem !important;margin-left:-.5rem !important}.mx-lg-n3{margin-right:-1rem !important;margin-left:-1rem !important}.mx-lg-n4{margin-right:-1.5rem !important;margin-left:-1.5rem !important}.mx-lg-n5{margin-right:-3rem !important;margin-left:-3rem !important}.my-lg-n1{margin-top:-.25rem !important;margin-bottom:-.25rem !important}.my-lg-n2{margin-top:-.5rem !important;margin-bottom:-.5rem !important}.my-lg-n3{margin-top:-1rem !important;margin-bottom:-1rem !important}.my-lg-n4{margin-top:-1.5rem !important;margin-bottom:-1.5rem !important}.my-lg-n5{margin-top:-3rem !important;margin-bottom:-3rem !important}.mt-lg-n1{margin-top:-.25rem !important}.mt-lg-n2{margin-top:-.5rem !important}.mt-lg-n3{margin-top:-1rem !important}.mt-lg-n4{margin-top:-1.5rem !important}.mt-lg-n5{margin-top:-3rem !important}.me-lg-n1{margin-right:-.25rem !important}.me-lg-n2{margin-right:-.5rem !important}.me-lg-n3{margin-right:-1rem !important}.me-lg-n4{margin-right:-1.5rem !important}.me-lg-n5{margin-right:-3rem !important}.mb-lg-n1{margin-bottom:-.25rem !important}.mb-lg-n2{margin-bottom:-.5rem !important}.mb-lg-n3{margin-bottom:-1rem !important}.mb-lg-n4{margin-bottom:-1.5rem !important}.mb-lg-n5{margin-bottom:-3rem !important}.ms-lg-n1{margin-left:-.25rem !important}.ms-lg-n2{margin-left:-.5rem !important}.ms-lg-n3{margin-left:-1rem !important}.ms-lg-n4{margin-left:-1.5rem !important}.ms-lg-n5{margin-left:-3rem !important}.p-lg-0{padding:0 !important}.p-lg-1{padding:.25rem !important}.p-lg-2{padding:.5rem !important}.p-lg-3{padding:1rem !important}.p-lg-4{padding:1.5rem !important}.p-lg-5{padding:3rem !important}.px-lg-0{padding-right:0 !important;padding-left:0 !important}.px-lg-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-lg-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-lg-3{padding-right:1rem !important;padding-left:1rem !important}.px-lg-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-lg-5{padding-right:3rem !important;padding-left:3rem !important}.py-lg-0{padding-top:0 !important;padding-bottom:0 !important}.py-lg-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-lg-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-lg-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-lg-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-lg-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-lg-0{padding-top:0 !important}.pt-lg-1{padding-top:.25rem !important}.pt-lg-2{padding-top:.5rem !important}.pt-lg-3{padding-top:1rem !important}.pt-lg-4{padding-top:1.5rem !important}.pt-lg-5{padding-top:3rem !important}.pe-lg-0{padding-right:0 !important}.pe-lg-1{padding-right:.25rem !important}.pe-lg-2{padding-right:.5rem !important}.pe-lg-3{padding-right:1rem !important}.pe-lg-4{padding-right:1.5rem !important}.pe-lg-5{padding-right:3rem !important}.pb-lg-0{padding-bottom:0 !important}.pb-lg-1{padding-bottom:.25rem !important}.pb-lg-2{padding-bottom:.5rem !important}.pb-lg-3{padding-bottom:1rem !important}.pb-lg-4{padding-bottom:1.5rem !important}.pb-lg-5{padding-bottom:3rem !important}.ps-lg-0{padding-left:0 !important}.ps-lg-1{padding-left:.25rem !important}.ps-lg-2{padding-left:.5rem !important}.ps-lg-3{padding-left:1rem !important}.ps-lg-4{padding-left:1.5rem !important}.ps-lg-5{padding-left:3rem !important}.text-lg-start{text-align:left !important}.text-lg-end{text-align:right !important}.text-lg-center{text-align:center !important}}@media (min-width: 1200px){.float-xl-start{float:left !important}.float-xl-end{float:right !important}.float-xl-none{float:none !important}.d-xl-inline{display:inline !important}.d-xl-inline-block{display:inline-block !important}.d-xl-block{display:block !important}.d-xl-grid{display:grid !important}.d-xl-table{display:table !important}.d-xl-table-row{display:table-row !important}.d-xl-table-cell{display:table-cell !important}.d-xl-flex{display:flex !important}.d-xl-inline-flex{display:inline-flex !important}.d-xl-none{display:none !important}.flex-xl-fill{flex:1 1 auto !important}.flex-xl-row{flex-direction:row !important}.flex-xl-column{flex-direction:column !important}.flex-xl-row-reverse{flex-direction:row-reverse !important}.flex-xl-column-reverse{flex-direction:column-reverse !important}.flex-xl-grow-0{flex-grow:0 !important}.flex-xl-grow-1{flex-grow:1 !important}.flex-xl-shrink-0{flex-shrink:0 !important}.flex-xl-shrink-1{flex-shrink:1 !important}.flex-xl-wrap{flex-wrap:wrap !important}.flex-xl-nowrap{flex-wrap:nowrap !important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xl-0{gap:0 !important}.gap-xl-1{gap:.25rem !important}.gap-xl-2{gap:.5rem !important}.gap-xl-3{gap:1rem !important}.gap-xl-4{gap:1.5rem !important}.gap-xl-5{gap:3rem !important}.justify-content-xl-start{justify-content:flex-start !important}.justify-content-xl-end{justify-content:flex-end !important}.justify-content-xl-center{justify-content:center !important}.justify-content-xl-between{justify-content:space-between !important}.justify-content-xl-around{justify-content:space-around !important}.justify-content-xl-evenly{justify-content:space-evenly !important}.align-items-xl-start{align-items:flex-start !important}.align-items-xl-end{align-items:flex-end !important}.align-items-xl-center{align-items:center !important}.align-items-xl-baseline{align-items:baseline !important}.align-items-xl-stretch{align-items:stretch !important}.align-content-xl-start{align-content:flex-start !important}.align-content-xl-end{align-content:flex-end !important}.align-content-xl-center{align-content:center !important}.align-content-xl-between{align-content:space-between !important}.align-content-xl-around{align-content:space-around !important}.align-content-xl-stretch{align-content:stretch !important}.align-self-xl-auto{align-self:auto !important}.align-self-xl-start{align-self:flex-start !important}.align-self-xl-end{align-self:flex-end !important}.align-self-xl-center{align-self:center !important}.align-self-xl-baseline{align-self:baseline !important}.align-self-xl-stretch{align-self:stretch !important}.order-xl-first{order:-1 !important}.order-xl-0{order:0 !important}.order-xl-1{order:1 !important}.order-xl-2{order:2 !important}.order-xl-3{order:3 !important}.order-xl-4{order:4 !important}.order-xl-5{order:5 !important}.order-xl-last{order:6 !important}.m-xl-0{margin:0 !important}.m-xl-1{margin:.25rem !important}.m-xl-2{margin:.5rem !important}.m-xl-3{margin:1rem !important}.m-xl-4{margin:1.5rem !important}.m-xl-5{margin:3rem !important}.m-xl-auto{margin:auto !important}.mx-xl-0{margin-right:0 !important;margin-left:0 !important}.mx-xl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xl-auto{margin-right:auto !important;margin-left:auto !important}.my-xl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xl-0{margin-top:0 !important}.mt-xl-1{margin-top:.25rem !important}.mt-xl-2{margin-top:.5rem !important}.mt-xl-3{margin-top:1rem !important}.mt-xl-4{margin-top:1.5rem !important}.mt-xl-5{margin-top:3rem !important}.mt-xl-auto{margin-top:auto !important}.me-xl-0{margin-right:0 !important}.me-xl-1{margin-right:.25rem !important}.me-xl-2{margin-right:.5rem !important}.me-xl-3{margin-right:1rem !important}.me-xl-4{margin-right:1.5rem !important}.me-xl-5{margin-right:3rem !important}.me-xl-auto{margin-right:auto !important}.mb-xl-0{margin-bottom:0 !important}.mb-xl-1{margin-bottom:.25rem !important}.mb-xl-2{margin-bottom:.5rem !important}.mb-xl-3{margin-bottom:1rem !important}.mb-xl-4{margin-bottom:1.5rem !important}.mb-xl-5{margin-bottom:3rem !important}.mb-xl-auto{margin-bottom:auto !important}.ms-xl-0{margin-left:0 !important}.ms-xl-1{margin-left:.25rem !important}.ms-xl-2{margin-left:.5rem !important}.ms-xl-3{margin-left:1rem !important}.ms-xl-4{margin-left:1.5rem !important}.ms-xl-5{margin-left:3rem !important}.ms-xl-auto{margin-left:auto !important}.m-xl-n1{margin:-.25rem !important}.m-xl-n2{margin:-.5rem !important}.m-xl-n3{margin:-1rem !important}.m-xl-n4{margin:-1.5rem !important}.m-xl-n5{margin:-3rem !important}.mx-xl-n1{margin-right:-.25rem !important;margin-left:-.25rem !important}.mx-xl-n2{margin-right:-.5rem !important;margin-left:-.5rem !important}.mx-xl-n3{margin-right:-1rem !important;margin-left:-1rem !important}.mx-xl-n4{margin-right:-1.5rem !important;margin-left:-1.5rem !important}.mx-xl-n5{margin-right:-3rem !important;margin-left:-3rem !important}.my-xl-n1{margin-top:-.25rem !important;margin-bottom:-.25rem !important}.my-xl-n2{margin-top:-.5rem !important;margin-bottom:-.5rem !important}.my-xl-n3{margin-top:-1rem !important;margin-bottom:-1rem !important}.my-xl-n4{margin-top:-1.5rem !important;margin-bottom:-1.5rem !important}.my-xl-n5{margin-top:-3rem !important;margin-bottom:-3rem !important}.mt-xl-n1{margin-top:-.25rem !important}.mt-xl-n2{margin-top:-.5rem !important}.mt-xl-n3{margin-top:-1rem !important}.mt-xl-n4{margin-top:-1.5rem !important}.mt-xl-n5{margin-top:-3rem !important}.me-xl-n1{margin-right:-.25rem !important}.me-xl-n2{margin-right:-.5rem !important}.me-xl-n3{margin-right:-1rem !important}.me-xl-n4{margin-right:-1.5rem !important}.me-xl-n5{margin-right:-3rem !important}.mb-xl-n1{margin-bottom:-.25rem !important}.mb-xl-n2{margin-bottom:-.5rem !important}.mb-xl-n3{margin-bottom:-1rem !important}.mb-xl-n4{margin-bottom:-1.5rem !important}.mb-xl-n5{margin-bottom:-3rem !important}.ms-xl-n1{margin-left:-.25rem !important}.ms-xl-n2{margin-left:-.5rem !important}.ms-xl-n3{margin-left:-1rem !important}.ms-xl-n4{margin-left:-1.5rem !important}.ms-xl-n5{margin-left:-3rem !important}.p-xl-0{padding:0 !important}.p-xl-1{padding:.25rem !important}.p-xl-2{padding:.5rem !important}.p-xl-3{padding:1rem !important}.p-xl-4{padding:1.5rem !important}.p-xl-5{padding:3rem !important}.px-xl-0{padding-right:0 !important;padding-left:0 !important}.px-xl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xl-0{padding-top:0 !important}.pt-xl-1{padding-top:.25rem !important}.pt-xl-2{padding-top:.5rem !important}.pt-xl-3{padding-top:1rem !important}.pt-xl-4{padding-top:1.5rem !important}.pt-xl-5{padding-top:3rem !important}.pe-xl-0{padding-right:0 !important}.pe-xl-1{padding-right:.25rem !important}.pe-xl-2{padding-right:.5rem !important}.pe-xl-3{padding-right:1rem !important}.pe-xl-4{padding-right:1.5rem !important}.pe-xl-5{padding-right:3rem !important}.pb-xl-0{padding-bottom:0 !important}.pb-xl-1{padding-bottom:.25rem !important}.pb-xl-2{padding-bottom:.5rem !important}.pb-xl-3{padding-bottom:1rem !important}.pb-xl-4{padding-bottom:1.5rem !important}.pb-xl-5{padding-bottom:3rem !important}.ps-xl-0{padding-left:0 !important}.ps-xl-1{padding-left:.25rem !important}.ps-xl-2{padding-left:.5rem !important}.ps-xl-3{padding-left:1rem !important}.ps-xl-4{padding-left:1.5rem !important}.ps-xl-5{padding-left:3rem !important}.text-xl-start{text-align:left !important}.text-xl-end{text-align:right !important}.text-xl-center{text-align:center !important}}@media (min-width: 1400px){.float-xxl-start{float:left !important}.float-xxl-end{float:right !important}.float-xxl-none{float:none !important}.d-xxl-inline{display:inline !important}.d-xxl-inline-block{display:inline-block !important}.d-xxl-block{display:block !important}.d-xxl-grid{display:grid !important}.d-xxl-table{display:table !important}.d-xxl-table-row{display:table-row !important}.d-xxl-table-cell{display:table-cell !important}.d-xxl-flex{display:flex !important}.d-xxl-inline-flex{display:inline-flex !important}.d-xxl-none{display:none !important}.flex-xxl-fill{flex:1 1 auto !important}.flex-xxl-row{flex-direction:row !important}.flex-xxl-column{flex-direction:column !important}.flex-xxl-row-reverse{flex-direction:row-reverse !important}.flex-xxl-column-reverse{flex-direction:column-reverse !important}.flex-xxl-grow-0{flex-grow:0 !important}.flex-xxl-grow-1{flex-grow:1 !important}.flex-xxl-shrink-0{flex-shrink:0 !important}.flex-xxl-shrink-1{flex-shrink:1 !important}.flex-xxl-wrap{flex-wrap:wrap !important}.flex-xxl-nowrap{flex-wrap:nowrap !important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse !important}.gap-xxl-0{gap:0 !important}.gap-xxl-1{gap:.25rem !important}.gap-xxl-2{gap:.5rem !important}.gap-xxl-3{gap:1rem !important}.gap-xxl-4{gap:1.5rem !important}.gap-xxl-5{gap:3rem !important}.justify-content-xxl-start{justify-content:flex-start !important}.justify-content-xxl-end{justify-content:flex-end !important}.justify-content-xxl-center{justify-content:center !important}.justify-content-xxl-between{justify-content:space-between !important}.justify-content-xxl-around{justify-content:space-around !important}.justify-content-xxl-evenly{justify-content:space-evenly !important}.align-items-xxl-start{align-items:flex-start !important}.align-items-xxl-end{align-items:flex-end !important}.align-items-xxl-center{align-items:center !important}.align-items-xxl-baseline{align-items:baseline !important}.align-items-xxl-stretch{align-items:stretch !important}.align-content-xxl-start{align-content:flex-start !important}.align-content-xxl-end{align-content:flex-end !important}.align-content-xxl-center{align-content:center !important}.align-content-xxl-between{align-content:space-between !important}.align-content-xxl-around{align-content:space-around !important}.align-content-xxl-stretch{align-content:stretch !important}.align-self-xxl-auto{align-self:auto !important}.align-self-xxl-start{align-self:flex-start !important}.align-self-xxl-end{align-self:flex-end !important}.align-self-xxl-center{align-self:center !important}.align-self-xxl-baseline{align-self:baseline !important}.align-self-xxl-stretch{align-self:stretch !important}.order-xxl-first{order:-1 !important}.order-xxl-0{order:0 !important}.order-xxl-1{order:1 !important}.order-xxl-2{order:2 !important}.order-xxl-3{order:3 !important}.order-xxl-4{order:4 !important}.order-xxl-5{order:5 !important}.order-xxl-last{order:6 !important}.m-xxl-0{margin:0 !important}.m-xxl-1{margin:.25rem !important}.m-xxl-2{margin:.5rem !important}.m-xxl-3{margin:1rem !important}.m-xxl-4{margin:1.5rem !important}.m-xxl-5{margin:3rem !important}.m-xxl-auto{margin:auto !important}.mx-xxl-0{margin-right:0 !important;margin-left:0 !important}.mx-xxl-1{margin-right:.25rem !important;margin-left:.25rem !important}.mx-xxl-2{margin-right:.5rem !important;margin-left:.5rem !important}.mx-xxl-3{margin-right:1rem !important;margin-left:1rem !important}.mx-xxl-4{margin-right:1.5rem !important;margin-left:1.5rem !important}.mx-xxl-5{margin-right:3rem !important;margin-left:3rem !important}.mx-xxl-auto{margin-right:auto !important;margin-left:auto !important}.my-xxl-0{margin-top:0 !important;margin-bottom:0 !important}.my-xxl-1{margin-top:.25rem !important;margin-bottom:.25rem !important}.my-xxl-2{margin-top:.5rem !important;margin-bottom:.5rem !important}.my-xxl-3{margin-top:1rem !important;margin-bottom:1rem !important}.my-xxl-4{margin-top:1.5rem !important;margin-bottom:1.5rem !important}.my-xxl-5{margin-top:3rem !important;margin-bottom:3rem !important}.my-xxl-auto{margin-top:auto !important;margin-bottom:auto !important}.mt-xxl-0{margin-top:0 !important}.mt-xxl-1{margin-top:.25rem !important}.mt-xxl-2{margin-top:.5rem !important}.mt-xxl-3{margin-top:1rem !important}.mt-xxl-4{margin-top:1.5rem !important}.mt-xxl-5{margin-top:3rem !important}.mt-xxl-auto{margin-top:auto !important}.me-xxl-0{margin-right:0 !important}.me-xxl-1{margin-right:.25rem !important}.me-xxl-2{margin-right:.5rem !important}.me-xxl-3{margin-right:1rem !important}.me-xxl-4{margin-right:1.5rem !important}.me-xxl-5{margin-right:3rem !important}.me-xxl-auto{margin-right:auto !important}.mb-xxl-0{margin-bottom:0 !important}.mb-xxl-1{margin-bottom:.25rem !important}.mb-xxl-2{margin-bottom:.5rem !important}.mb-xxl-3{margin-bottom:1rem !important}.mb-xxl-4{margin-bottom:1.5rem !important}.mb-xxl-5{margin-bottom:3rem !important}.mb-xxl-auto{margin-bottom:auto !important}.ms-xxl-0{margin-left:0 !important}.ms-xxl-1{margin-left:.25rem !important}.ms-xxl-2{margin-left:.5rem !important}.ms-xxl-3{margin-left:1rem !important}.ms-xxl-4{margin-left:1.5rem !important}.ms-xxl-5{margin-left:3rem !important}.ms-xxl-auto{margin-left:auto !important}.m-xxl-n1{margin:-.25rem !important}.m-xxl-n2{margin:-.5rem !important}.m-xxl-n3{margin:-1rem !important}.m-xxl-n4{margin:-1.5rem !important}.m-xxl-n5{margin:-3rem !important}.mx-xxl-n1{margin-right:-.25rem !important;margin-left:-.25rem !important}.mx-xxl-n2{margin-right:-.5rem !important;margin-left:-.5rem !important}.mx-xxl-n3{margin-right:-1rem !important;margin-left:-1rem !important}.mx-xxl-n4{margin-right:-1.5rem !important;margin-left:-1.5rem !important}.mx-xxl-n5{margin-right:-3rem !important;margin-left:-3rem !important}.my-xxl-n1{margin-top:-.25rem !important;margin-bottom:-.25rem !important}.my-xxl-n2{margin-top:-.5rem !important;margin-bottom:-.5rem !important}.my-xxl-n3{margin-top:-1rem !important;margin-bottom:-1rem !important}.my-xxl-n4{margin-top:-1.5rem !important;margin-bottom:-1.5rem !important}.my-xxl-n5{margin-top:-3rem !important;margin-bottom:-3rem !important}.mt-xxl-n1{margin-top:-.25rem !important}.mt-xxl-n2{margin-top:-.5rem !important}.mt-xxl-n3{margin-top:-1rem !important}.mt-xxl-n4{margin-top:-1.5rem !important}.mt-xxl-n5{margin-top:-3rem !important}.me-xxl-n1{margin-right:-.25rem !important}.me-xxl-n2{margin-right:-.5rem !important}.me-xxl-n3{margin-right:-1rem !important}.me-xxl-n4{margin-right:-1.5rem !important}.me-xxl-n5{margin-right:-3rem !important}.mb-xxl-n1{margin-bottom:-.25rem !important}.mb-xxl-n2{margin-bottom:-.5rem !important}.mb-xxl-n3{margin-bottom:-1rem !important}.mb-xxl-n4{margin-bottom:-1.5rem !important}.mb-xxl-n5{margin-bottom:-3rem !important}.ms-xxl-n1{margin-left:-.25rem !important}.ms-xxl-n2{margin-left:-.5rem !important}.ms-xxl-n3{margin-left:-1rem !important}.ms-xxl-n4{margin-left:-1.5rem !important}.ms-xxl-n5{margin-left:-3rem !important}.p-xxl-0{padding:0 !important}.p-xxl-1{padding:.25rem !important}.p-xxl-2{padding:.5rem !important}.p-xxl-3{padding:1rem !important}.p-xxl-4{padding:1.5rem !important}.p-xxl-5{padding:3rem !important}.px-xxl-0{padding-right:0 !important;padding-left:0 !important}.px-xxl-1{padding-right:.25rem !important;padding-left:.25rem !important}.px-xxl-2{padding-right:.5rem !important;padding-left:.5rem !important}.px-xxl-3{padding-right:1rem !important;padding-left:1rem !important}.px-xxl-4{padding-right:1.5rem !important;padding-left:1.5rem !important}.px-xxl-5{padding-right:3rem !important;padding-left:3rem !important}.py-xxl-0{padding-top:0 !important;padding-bottom:0 !important}.py-xxl-1{padding-top:.25rem !important;padding-bottom:.25rem !important}.py-xxl-2{padding-top:.5rem !important;padding-bottom:.5rem !important}.py-xxl-3{padding-top:1rem !important;padding-bottom:1rem !important}.py-xxl-4{padding-top:1.5rem !important;padding-bottom:1.5rem !important}.py-xxl-5{padding-top:3rem !important;padding-bottom:3rem !important}.pt-xxl-0{padding-top:0 !important}.pt-xxl-1{padding-top:.25rem !important}.pt-xxl-2{padding-top:.5rem !important}.pt-xxl-3{padding-top:1rem !important}.pt-xxl-4{padding-top:1.5rem !important}.pt-xxl-5{padding-top:3rem !important}.pe-xxl-0{padding-right:0 !important}.pe-xxl-1{padding-right:.25rem !important}.pe-xxl-2{padding-right:.5rem !important}.pe-xxl-3{padding-right:1rem !important}.pe-xxl-4{padding-right:1.5rem !important}.pe-xxl-5{padding-right:3rem !important}.pb-xxl-0{padding-bottom:0 !important}.pb-xxl-1{padding-bottom:.25rem !important}.pb-xxl-2{padding-bottom:.5rem !important}.pb-xxl-3{padding-bottom:1rem !important}.pb-xxl-4{padding-bottom:1.5rem !important}.pb-xxl-5{padding-bottom:3rem !important}.ps-xxl-0{padding-left:0 !important}.ps-xxl-1{padding-left:.25rem !important}.ps-xxl-2{padding-left:.5rem !important}.ps-xxl-3{padding-left:1rem !important}.ps-xxl-4{padding-left:1.5rem !important}.ps-xxl-5{padding-left:3rem !important}.text-xxl-start{text-align:left !important}.text-xxl-end{text-align:right !important}.text-xxl-center{text-align:center !important}}@media (min-width: 1200px){.fs-1{font-size:2.5rem !important}.fs-2{font-size:2rem !important}.fs-3{font-size:1.75rem !important}.fs-4{font-size:1.5rem !important}}@media print{.d-print-inline{display:inline !important}.d-print-inline-block{display:inline-block !important}.d-print-block{display:block !important}.d-print-grid{display:grid !important}.d-print-table{display:table !important}.d-print-table-row{display:table-row !important}.d-print-table-cell{display:table-cell !important}.d-print-flex{display:flex !important}.d-print-inline-flex{display:inline-flex !important}.d-print-none{display:none !important}}@font-face{font-family:"Jost";font-style:normal;font-weight:400;font-display:swap;src:local("Jost"),url("fonts/vendor/jost/jost-v4-latin-regular.woff2") format("woff2"),url("fonts/vendor/jost/jost-v4-latin-regular.woff") format("woff");}@font-face{font-family:"Jost";font-style:normal;font-weight:500;font-display:swap;src:local("Jost"),url("fonts/vendor/jost/jost-v4-latin-500.woff2") format("woff2"),url("fonts/vendor/jost/jost-v4-latin-500.woff") format("woff");}@font-face{font-family:"Jost";font-style:normal;font-weight:700;font-display:swap;src:local("Jost"),url("fonts/vendor/jost/jost-v4-latin-700.woff2") format("woff2"),url("fonts/vendor/jost/jost-v4-latin-700.woff") format("woff");}@font-face{font-family:"Jost";font-style:italic;font-weight:400;font-display:swap;src:local("Jost"),url("fonts/vendor/jost/jost-v4-latin-italic.woff2") format("woff2"),url("fonts/vendor/jost/jost-v4-latin-italic.woff") format("woff");}@font-face{font-family:"Jost";font-style:italic;font-weight:500;font-display:swap;src:local("Jost"),url("fonts/vendor/jost/jost-v4-latin-500italic.woff2") format("woff2"),url("fonts/vendor/jost/jost-v4-latin-500italic.woff") format("woff");}@font-face{font-family:"Jost";font-style:italic;font-weight:700;font-display:swap;src:local("Jost"),url("fonts/vendor/jost/jost-v4-latin-700italic.woff2") format("woff2"),url("fonts/vendor/jost/jost-v4-latin-700italic.woff") format("woff");}.contributors .content,.blog .content,.page .content,.error404 .content,.docs.list .content,.tutorial.list .content,.showcase.list .content{padding-top:1rem;padding-bottom:3rem}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin:2rem 0 1rem}@media (min-width: 768px){body{font-size:1.125rem;padding-top:4rem !important}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{margin-bottom:1.125rem}}.home h1,.home .h1{font-size:calc(1.875rem + 1.5vw)}a:hover,a:focus{text-decoration:underline}a.btn:hover,.search-form a.search-submit:hover,a.btn:focus,.search-form a.search-submit:focus{text-decoration:none}.section{padding-top:5rem;padding-bottom:5rem}.section-md{padding-top:3rem;padding-bottom:3rem}.section-sm{padding-top:1rem;padding-bottom:1rem}body{padding-top:3.5625rem}.docs-sidebar{order:2}@media (min-width: 992px){.docs-sidebar{order:0;border-right:1px solid #e9ecef}@supports (position: -webkit-sticky) or (position: sticky){.docs-sidebar{position:-webkit-sticky;position:sticky;top:4rem;z-index:1000;height:calc(100vh - 4rem)}}}@media (min-width: 1200px){.docs-sidebar{flex:0 1 320px}}.docs-links{padding-bottom:5rem}@media (min-width: 992px){@supports (position: -webkit-sticky) or (position: sticky){.docs-links{max-height:calc(100vh - 4rem);overflow-y:scroll}}}@media (min-width: 992px){.docs-links{display:block;width:auto;margin-right:-1.5rem;padding-bottom:4rem}}.docs-toc{order:2}@supports (position: -webkit-sticky) or (position: sticky){.docs-toc{position:-webkit-sticky;position:sticky;top:4rem;height:calc(100vh - 4rem);overflow-y:auto}}.docs-content{padding-bottom:3rem;order:1}.docs-navigation,.blog-navigation{margin-top:2rem;margin-bottom:0;padding-top:2rem}.docs-navigation a,.blog-navigation a{font-size:.9rem}.docs-navigation{border-top:1px solid #e9ecef}@media (min-width: 992px){.docs-navigation,.blog-navigation{margin-bottom:-1rem}.docs-navigation a,.blog-navigation a{font-size:1rem}}.navbar a:hover,.navbar a:focus{text-decoration:none}#TableOfContents ul{padding-left:0;list-style:none}::selection{background:rgba(212,53,159,.2)}.bg-dots{background-image:radial-gradient(#dee2e6 15%, rgba(0,0,0,0) 15%);background-position:0 0;background-size:1rem 1rem;-webkit-mask:linear-gradient(to top, #fff, rgba(0,0,0,0));mask:linear-gradient(to top, #fff, rgba(0,0,0,0));width:100%;height:9rem;margin-top:-10rem;z-index:-1}.bg-dots-md{margin-top:-11rem}.bg-dots-lg{margin-top:-12rem}.gradient-text{background-color:#5d2f86;background-image:linear-gradient(90deg, #5d2f86, #8ed6fb 50%, #d32e9d);background-size:100%;background-repeat:repeat;-webkit-background-clip:text;-moz-background-clip:text;-webkit-text-fill-color:rgba(0,0,0,0);-moz-text-fill-color:rgba(0,0,0,0)}.katex{font-size:1.125rem}article hr{margin:3rem 0}body.dark{background:#212529;color:#dee2e6}body.dark a{color:#8ed6fb}body.dark a.text-body{color:#dee2e6 !important}body.dark .btn-primary{color:#1d2d35;background-color:#8ed6fb;border-color:#8ed6fb;color:#212529 !important}body.dark .btn-primary:hover{color:#1d2d35;background-color:#9fdcfc;border-color:#99dafb}.btn-check:focus+body.dark .btn-primary,body.dark .btn-primary:focus{color:#1d2d35;background-color:#9fdcfc;border-color:#99dafb;box-shadow:0 0 0 .25rem rgba(125,189,221,.5)}.btn-check:checked+body.dark .btn-primary,.btn-check:active+body.dark .btn-primary,body.dark .btn-primary:active,body.dark .btn-primary.active,.show>body.dark .btn-primary.dropdown-toggle{color:#1d2d35;background-color:#a5defc;border-color:#99dafb}.btn-check:checked+body.dark .btn-primary:focus,.btn-check:active+body.dark .btn-primary:focus,body.dark .btn-primary:active:focus,body.dark .btn-primary.active:focus,.show>body.dark .btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(125,189,221,.5)}body.dark .btn-primary:disabled,body.dark .btn-primary.disabled{color:#1d2d35;background-color:#8ed6fb;border-color:#8ed6fb}body.dark .btn-outline-primary{color:#8ed6fb;border-color:#8ed6fb;color:#8ed6fb}body.dark .btn-outline-primary:hover{color:#8ed6fb;background-color:#8ed6fb;border-color:#8ed6fb}.btn-check:focus+body.dark .btn-outline-primary,body.dark .btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(142,214,251,.5)}.btn-check:checked+body.dark .btn-outline-primary,.btn-check:active+body.dark .btn-outline-primary,body.dark .btn-outline-primary:active,body.dark .btn-outline-primary.active,body.dark .btn-outline-primary.dropdown-toggle.show{color:#1d2d35;background-color:#8ed6fb;border-color:#8ed6fb}.btn-check:checked+body.dark .btn-outline-primary:focus,.btn-check:active+body.dark .btn-outline-primary:focus,body.dark .btn-outline-primary:active:focus,body.dark .btn-outline-primary.active:focus,body.dark .btn-outline-primary.dropdown-toggle.show:focus{box-shadow:0 0 0 .25rem rgba(142,214,251,.5)}body.dark .btn-outline-primary:disabled,body.dark .btn-outline-primary.disabled{color:#8ed6fb;background-color:rgba(0,0,0,0)}body.dark .btn-outline-primary:hover{color:#212529}body.dark .navbar{background:#212529;opacity:.975;border-bottom:1px solid #1b1f22}body.dark.home .navbar{border-bottom:0}body.dark .navbar-light .navbar-brand{color:#dee2e6 !important}body.dark .navbar-light .navbar-nav .nav-link,body.dark .navbar-light .navbar-nav .banner .nav a,.banner .nav body.dark .navbar-light .navbar-nav a{color:#dee2e6}body.dark .navbar-light .navbar-nav .nav-link:hover,body.dark .navbar-light .navbar-nav .banner .nav a:hover,.banner .nav body.dark .navbar-light .navbar-nav a:hover,body.dark .navbar-light .navbar-nav .nav-link:focus,body.dark .navbar-light .navbar-nav .banner .nav a:focus,.banner .nav body.dark .navbar-light .navbar-nav a:focus{color:#8ed6fb}body.dark .navbar-light .navbar-nav .nav-link.disabled,body.dark .navbar-light .navbar-nav .banner .nav a.disabled,.banner .nav body.dark .navbar-light .navbar-nav a.disabled{color:rgba(255,255,255,.25)}body.dark .navbar-light .navbar-nav .show>.nav-link,body.dark .navbar-light .navbar-nav .banner .nav .show>a,.banner .nav body.dark .navbar-light .navbar-nav .show>a,body.dark .navbar-light .navbar-nav .active>.nav-link,body.dark .navbar-light .navbar-nav .banner .nav .active>a,.banner .nav body.dark .navbar-light .navbar-nav .active>a,body.dark .navbar-light .navbar-nav .nav-link.show,body.dark .navbar-light .navbar-nav .banner .nav a.show,.banner .nav body.dark .navbar-light .navbar-nav a.show,body.dark .navbar-light .navbar-nav .nav-link.active,body.dark .navbar-light .navbar-nav .banner .nav a.active,.banner .nav body.dark .navbar-light .navbar-nav a.active{color:#8ed6fb}body.dark .navbar-light .navbar-text{color:#dee2e6}body.dark .alert-primary a{color:#212529}body.dark .alert-warning{background:#1b1f22;color:#dee2e6}body.dark .page-links a{color:#dee2e6}body.dark .showcase-meta a{color:#dee2e6}body.dark .showcase-meta a:hover,body.dark .showcase-meta a:focus{color:#8ed6fb}body.dark .docs-link:hover,body.dark .docs-link.active,body.dark .page-links a:hover{text-decoration:none;color:#8ed6fb}body.dark .navbar-light .navbar-text a{color:#8ed6fb}body.dark .docs-links h3.sidebar-link a,body.dark .docs-links .sidebar-link.h3 a,body.dark .page-links h3.sidebar-link a,body.dark .page-links .sidebar-link.h3 a{color:#dee2e6}body.dark .navbar-light .navbar-text a:hover,body.dark .navbar-light .navbar-text a:focus{color:#8ed6fb}body.dark .navbar .btn-link{color:#dee2e6}body.dark .content .btn-link{color:#8ed6fb}body.dark .content .btn-link:hover{color:#8ed6fb}body.dark .navbar .btn-link:hover{color:#8ed6fb}body.dark .navbar .btn-link:active{color:#8ed6fb}body.dark .form-control.is-search,body.dark .search-form .is-search.search-field,.search-form body.dark .is-search.search-field,body.dark .comment-form input.is-search[type=text],.comment-form body.dark input.is-search[type=text],body.dark .comment-form input.is-search[type=email],.comment-form body.dark input.is-search[type=email],body.dark .comment-form input.is-search[type=url],.comment-form body.dark input.is-search[type=url],body.dark .comment-form textarea.is-search,.comment-form body.dark textarea.is-search{background:#1b1f22;}body.dark .navbar-form::after{color:#6c757d;border:1px solid #343a40}body.dark .form-control,body.dark .search-form .search-field,.search-form body.dark .search-field,body.dark .comment-form input[type=text],.comment-form body.dark input[type=text],body.dark .comment-form input[type=email],.comment-form body.dark input[type=email],body.dark .comment-form input[type=url],.comment-form body.dark input[type=url],body.dark .comment-form textarea,.comment-form body.dark textarea{color:#adb5bd}body.dark .form-control:focus,body.dark .search-form .search-field:focus,.search-form body.dark .search-field:focus,body.dark .comment-form input[type=text]:focus,.comment-form body.dark input[type=text]:focus,body.dark .comment-form input[type=email]:focus,.comment-form body.dark input[type=email]:focus,body.dark .comment-form input[type=url]:focus,.comment-form body.dark input[type=url]:focus,body.dark .comment-form textarea:focus,.comment-form body.dark textarea:focus{box-shadow:0 0 0 .2rem #9adafb}body.dark .border-top{border-top:1px solid #1b1f22 !important}@media (min-width: 992px){body.dark .docs-sidebar{order:0;border-right:1px solid #1b1f22}}body.dark .docs-navigation{border-top:1px solid #1b1f22}body.dark pre code::-webkit-scrollbar-thumb{background:#1b1f22}body.dark pre:not(.hljs){background:#1b1f22;color:#dee2e6}body.dark pre code:hover{scrollbar-width:thin;scrollbar-color:#1b1f22 rgba(0,0,0,0)}body.dark pre code::-webkit-scrollbar-thumb:hover{background:#1b1f22}body.dark blockquote{border-left:3px solid #1b1f22}body.dark .footer{border-top:1px solid #1b1f22}body.dark .docs-links,body.dark .docs-toc{scrollbar-width:thin;scrollbar-color:#212529 #212529}body.dark .docs-links::-webkit-scrollbar,body.dark .docs-toc::-webkit-scrollbar{width:5px}body.dark .docs-links::-webkit-scrollbar-track,body.dark .docs-toc::-webkit-scrollbar-track{background:#212529}body.dark .docs-links::-webkit-scrollbar-thumb,body.dark .docs-toc::-webkit-scrollbar-thumb{background:#212529}body.dark .docs-links:hover,body.dark .docs-toc:hover{scrollbar-width:thin;scrollbar-color:#1b1f22 #212529}body.dark .docs-links:hover::-webkit-scrollbar-thumb,body.dark .docs-toc:hover::-webkit-scrollbar-thumb{background:#1b1f22}body.dark .docs-links::-webkit-scrollbar-thumb:hover,body.dark .docs-toc::-webkit-scrollbar-thumb:hover{background:#1b1f22}body.dark .docs-links h3:not(:first-child),body.dark .docs-links .h3:not(:first-child){border-top:1px solid #1b1f22}body.dark a.docs-link{color:#dee2e6}body.dark .page-links li:not(:first-child){border-top:1px dashed #1b1f22}body.dark .card{background:#212529;border:1px solid #1b1f22}body.dark .card.bg-light{background:#1b1f22 !important}body.dark .navbar .menu-icon .navicon{background:#dee2e6}body.dark .navbar .menu-icon .navicon::before,body.dark .navbar .menu-icon .navicon::after{background:#dee2e6}body.dark .logo-light{display:none !important}body.dark .logo-dark{display:inline-block !important}body.dark .bg-light{background:#1e2125 !important}body.dark .bg-dots{background-image:radial-gradient(#556370 15%, rgba(0,0,0,0) 15%)}body.dark .text-muted{color:#c8cfd6 !important}body.dark .alert-primary{background:#8ed6fb;color:#212529}body.dark .figure-caption{color:#dee2e6}body.dark .copy-status::after{content:"Copy";display:block;color:#dee2e6}body.dark .copy-status:hover::after{content:"Copy";display:block;color:#8ed6fb}body.dark .copy-status:focus::after,body.dark .copy-status:active::after{content:"Copied";display:block;color:#8ed6fb}.hljs{display:block;overflow-x:auto;padding:.5em;background:#fbf7f0;color:#1d2d35}.hljs-string,.hljs-variable,.hljs-template-variable,.hljs-symbol,.hljs-bullet,.hljs-section,.hljs-addition,.hljs-attribute,.hljs-link{color:#d32e9d}.hljs-comment,.hljs-quote,.hljs-meta,.hljs-deletion{color:#888}.hljs-keyword,.hljs-selector-tag,.hljs-section,.hljs-name,.hljs-type,.hljs-strong{font-weight:bold}.hljs-emphasis{font-style:italic}body.dark .hljs{background:#1b1f22;color:#dee2e6}body.dark .hljs-string,body.dark .hljs-variable,body.dark .hljs-template-variable,body.dark .hljs-symbol,body.dark .hljs-bullet,body.dark .hljs-section,body.dark .hljs-addition,body.dark .hljs-attribute,body.dark .hljs-link{color:#8ed6fb}pre,code,kbd,samp{font-family:sfmono-regular,menlo,monaco,consolas,"Liberation Mono","Courier New",monospace;font-size:.875rem;border-radius:.25rem}code{padding:.25rem .5rem}pre{background:#fbf7f0;color:#1d2d35;margin:2rem 0}pre code{display:block;overflow-x:auto;line-height:1.5;padding:1.25rem 1.5rem;tab-size:4;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,0) rgba(0,0,0,0)}.hljs{padding:1.25rem 1.5rem}@media (max-width: 575.98px){pre,code,kbd,samp{border-radius:0}pre{margin:2rem -1.5rem}}pre code::-webkit-scrollbar{height:5px}pre code::-webkit-scrollbar-thumb{background:#e9ecef}pre code:hover{scrollbar-width:thin;scrollbar-color:#e9ecef rgba(0,0,0,0)}pre code::-webkit-scrollbar-thumb:hover{background:#e9ecef}.alert{font-family:sfmono-regular,menlo,monaco,consolas,"Liberation Mono","Courier New",monospace;font-size:.875rem}.alert-icon{margin-right:.75rem}.docs .alert{margin:2rem -1.5rem}.alert .alert-link{text-decoration:underline}.alert-dark{color:#fff;background-color:#1d2d35}.alert-dark .alert-link{color:#fff}.alert-light{color:#1d2d35}.alert-warning{background:#fbf7f0;color:#1d2d35}.alert-white{background-color:rgba(255,255,255,.95)}.alert-primary{color:#fff;background-color:#5d2f86}.alert-primary .alert-link{color:#fff}.alert .alert-link:hover,.alert .alert-link:focus{text-decoration:none}.navbar .btn-link{color:#1d2d35;padding:.4375rem 0}#mode{margin-right:1.25rem}.btn-link:focus{outline:0;box-shadow:none}#navigation{margin-left:1.25rem}@media (min-width: 768px){#mode{margin-right:.5rem}.navbar .btn-link{padding:.5625em .25rem .5rem .125rem}}.navbar .btn-link:hover{color:#5d2f86}.navbar .btn-link:active{color:#5d2f86}body .toggle-dark{display:block}body .toggle-light{display:none}body.dark .toggle-light{display:block}body.dark .toggle-dark{display:none}.btn-clipboard{display:none}@media (min-width: 768px){.doks-clipboard{position:relative;float:right}.btn-clipboard{position:absolute;top:1rem;right:.25rem;z-index:10;display:block;padding:.25rem .5rem;font-size:.875rem}}.copy-status::after{content:"Copy";display:block;color:#1d2d35}.copy-status:hover::after{content:"Copy";display:block;color:#d32e9d}.copy-status:focus::after,.copy-status:active::after{content:"Copied";display:block;color:#d32e9d}.comment-list ol{list-style:none}blockquote{margin-bottom:1rem;font-size:1.25rem;border-left:3px solid #dee2e6;padding-left:1rem}.footnote-definition sup{top:-.2 .5em;font-size:.75em;display:inline}.footnote-definition p{display:inline}.search-form label{font-weight:normal}figure{margin:2rem 0}.figure-caption{margin:.25rem 0 .75rem}figure.wide{margin:2rem -1.5rem}figure.wide .figure-caption{margin:.25rem 1.5rem .75rem}@media (min-width: 768px){figure.wide{margin:2rem -2.5rem}figure.wide .figure-caption{margin:.25rem 2.5rem .75rem}}@media (min-width: 992px){figure.wide{margin:2rem -5rem}figure.wide .figure-caption{margin:.25rem 5rem .75rem}}.blur-up{filter:blur(5px)}.blur-up.lazyloaded{filter:unset}.img-simple{margin-top:.375rem;margin-bottom:1.25rem}.navbar-form{position:relative}#suggestions{position:absolute;right:0;margin-top:.5rem;width:calc(100vw - 3rem);max-height:500px;overflow:auto}#suggestions a{display:block;text-decoration:none;padding:.75rem;margin:0 .5rem}#suggestions a:focus{background:#f8f9fa;outline:0}#suggestions div:not(:first-child){border-top:1px dashed #e9ecef}#suggestions div:first-child{margin-top:.5rem}#suggestions div:last-child{margin-bottom:.5rem}#suggestions a:hover{background:#f8f9fa}#suggestions span{font-size:1rem}#suggestions span:first-child{font-weight:700;color:#1d2d35}#suggestions span:nth-child(2){color:#495057}@media (min-width: 576px){#suggestions{width:30rem}#suggestions a{display:flex}#suggestions span:first-child{width:9rem;padding-right:1rem;border-right:1px solid #e9ecef;display:inline-block;text-align:right}#suggestions span:nth-child(2){width:19rem;padding-left:1rem}}table{margin:3rem 0}.footer{border-top:1px solid #e9ecef;padding-top:1.125rem;padding-bottom:1.125rem}.footer ul{margin-bottom:0}.footer li{font-size:.875rem;margin-bottom:0}@media (min-width: 768px){.footer li{font-size:1rem}}.navbar-text{margin-left:1rem}.navbar-brand{font-weight:700}.navbar-light .navbar-brand,.navbar-light .navbar-brand:hover,.navbar-light .navbar-brand:active{color:#1d2d35}.navbar-light .navbar-nav .active .nav-link,.navbar-light .navbar-nav .active .banner .nav a,.banner .nav .navbar-light .navbar-nav .active a{color:#5d2f86}@media (min-width: 768px){.navbar-brand{font-size:1.375rem}.navbar-text{margin-left:1.25rem}}.navbar-nav{flex-direction:row}.nav-item,.banner .nav li{margin-left:1.25rem}@media (min-width: 768px){.nav-item,.banner .nav li{margin-left:.5rem}}@media (max-width: 575.98px){.nav-item:first-child,.banner .nav li:first-child{margin-left:0}}@media (max-width: 767.98px){.navbar .container{padding-left:1.5rem;padding-right:1.5rem}}.break{flex-basis:100%;height:0}.navbar{background-color:rgba(255,255,255,.95);border-bottom:1px solid #e9ecef;margin-top:4px}.header-bar{border-top:4px solid;border-image-source:linear-gradient(90deg, #5d2f86, #8ed6fb 50%, #d32e9d);border-image-slice:1}.home .navbar{border-bottom:0}.navbar-form{position:relative;margin-top:.25rem}@media (min-width: 768px){.navbar-brand{margin-right:1rem !important}.main-nav .nav-item:first-child .nav-link,.main-nav .banner .nav li:first-child .nav-link,.banner .nav .main-nav li:first-child .nav-link,.main-nav .nav-item:first-child .banner .nav a,.banner .nav .main-nav .nav-item:first-child a,.main-nav .banner .nav li:first-child a,.banner .nav .main-nav li:first-child a,.fork-me .nav-item:first-child .nav-link,.fork-me .banner .nav li:first-child .nav-link,.banner .nav .fork-me li:first-child .nav-link,.fork-me .nav-item:first-child .banner .nav a,.banner .nav .fork-me .nav-item:first-child a,.fork-me .banner .nav li:first-child a,.banner .nav .fork-me li:first-child a{padding-left:0}.main-nav .nav-item:last-child .nav-link,.main-nav .banner .nav li:last-child .nav-link,.banner .nav .main-nav li:last-child .nav-link,.main-nav .nav-item:last-child .banner .nav a,.banner .nav .main-nav .nav-item:last-child a,.main-nav .banner .nav li:last-child a,.banner .nav .main-nav li:last-child a,.fork-me .nav-item:last-child .nav-link,.fork-me .banner .nav li:last-child .nav-link,.banner .nav .fork-me li:last-child .nav-link,.fork-me .nav-item:last-child .banner .nav a,.banner .nav .fork-me .nav-item:last-child a,.fork-me .banner .nav li:last-child a,.banner .nav .fork-me li:last-child a{padding-right:0}.navbar-form{margin-top:0;margin-left:6rem;margin-right:1.5rem}}@media (min-width: 992px){.navbar-form{margin-left:15rem}}@media (min-width: 1200px){.navbar-form{margin-left:30rem}}.form-control.is-search,.comment-form input.is-search[type=text],.comment-form input.is-search[type=email],.comment-form input.is-search[type=url],.comment-form textarea.is-search,.search-form .is-search.search-field{padding-right:2.5rem;background:#f8f9fa;border:0;}.navbar-form::after{position:absolute;top:.4625rem;right:.5375rem;display:flex;align-items:center;justify-content:center;height:1.5rem;padding-right:.4375rem;padding-left:.4375rem;font-size:.75rem;color:#495057;content:"/";border:1px solid #dee2e6;border-radius:.25rem}/*! purgecss start ignore */.algolia-autocomplete{display:flex !important}.algolia-autocomplete .ds-dropdown-menu{box-shadow:0 .5rem 1rem rgba(0,0,0,.15) !important}@media (max-width: 575.98px){.algolia-autocomplete .ds-dropdown-menu{max-width:512px !important;min-width:312px !important;width:auto !important}.algolia-autocomplete .algolia-docsearch-suggestion .algolia-docsearch-suggestion--subcategory-column::after{content:"|";margin-right:.25rem}}.algolia-autocomplete .algolia-docsearch-suggestion--title{margin-bottom:0}.algolia-autocomplete .algolia-docsearch-suggestion--highlight{padding:0 .05em}.algolia-autocomplete .algolia-docsearch-footer{margin-top:1rem;margin-right:.5rem;margin-bottom:.5rem}/*! purgecss end ignore */.navbar .menu-icon{cursor:pointer;padding:1.125rem .625rem;margin:0 0 0 -.625rem;user-select:none}.navbar .menu-icon .navicon{background:#1d2d35;display:block;height:2px;position:relative;transition:background .2s ease-out;width:18px}.navbar .menu-icon .navicon::before,.navbar .menu-icon .navicon::after{background:#1d2d35;content:"";display:block;height:100%;position:absolute;transition:all .2s ease-out;width:100%}.navbar .menu-icon .navicon::before{top:5px}.navbar .menu-icon .navicon::after{top:-5px}.navbar .menu-btn{display:none}.navbar .menu-btn:checked~.navbar-collapse{display:block;max-height:100vh}.navbar .menu-btn:checked~.menu-icon .navicon{background:rgba(0,0,0,0)}.navbar .menu-btn:checked~.menu-icon .navicon::before{transform:rotate(-45deg)}.navbar .menu-btn:checked~.menu-icon .navicon::after{transform:rotate(45deg)}.navbar .menu-btn:checked~.menu-icon:not(.steps) .navicon::before,.navbar .menu-btn:checked~.menu-icon:not(.steps) .navicon::after{top:0}.docs-content>h2[id]::before,.docs-content>[id].h2::before,.docs-content>h3[id]::before,.docs-content>[id].h3::before,.docs-content>h4[id]::before,.docs-content>[id].h4::before{display:block;height:6rem;margin-top:-6rem;content:""}.anchor{visibility:hidden;padding-left:.5rem}h1:hover a,.h1:hover a,h2:hover a,.h2:hover a,h3:hover a,.h3:hover a,h4:hover a,.h4:hover a{visibility:visible;text-decoration:none}.card-list{margin-top:2.25rem}.edit-page{margin-top:3rem;font-size:1rem}.edit-page svg{margin-right:.5rem;margin-bottom:.25rem}p.meta{margin-top:.5rem;font-size:1rem}.breadcrumb{margin-top:2.25rem;font-size:1rem}.page-header{text-align:center;margin-bottom:2rem}.home .card,.contributors.list .card,.blog.list .card{margin-top:2rem;margin-bottom:2rem;transition:transform .3s}.home .card:hover,.contributors.list .card:hover,.blog.list .card:hover{transform:scale(1.025)}.home .card-body,.contributors.list .card-body,.blog.list .card-body{padding:0 2rem 1rem}.blog-header{text-align:center;margin-bottom:2rem}.blog-footer{text-align:center}.docs-links,.docs-toc{scrollbar-width:thin;scrollbar-color:#fff #fff}.docs-links::-webkit-scrollbar,.docs-toc::-webkit-scrollbar{width:5px}.docs-links::-webkit-scrollbar-track,.docs-toc::-webkit-scrollbar-track{background:#fff}.docs-links::-webkit-scrollbar-thumb,.docs-toc::-webkit-scrollbar-thumb{background:#fff}.docs-links:hover,.docs-toc:hover{scrollbar-width:thin;scrollbar-color:#e9ecef #fff}.docs-links:hover::-webkit-scrollbar-thumb,.docs-toc:hover::-webkit-scrollbar-thumb{background:#e9ecef}.docs-links::-webkit-scrollbar-thumb:hover,.docs-toc::-webkit-scrollbar-thumb:hover{background:#e9ecef}.docs-links h3,.docs-links .h3,.page-links h3,.page-links .h3{text-transform:uppercase;font-size:1rem;margin:1.25rem 0 .5rem 0;padding:1.5rem 0 0 0}@media (min-width: 992px){.docs-links h3,.docs-links .h3,.page-links h3,.page-links .h3{margin:1.125rem 1.5rem .75rem 0;padding:1.375rem 0 0 0}}.docs-links h3:not(:first-child),.docs-links .h3:not(:first-child){border-top:1px solid #e9ecef}a.docs-link{color:#1d2d35;display:block;padding:.125rem 0;font-size:1rem}.page-links li{margin-top:.375rem;padding-top:.375rem}.page-links ul ul li{border-top:none;padding-left:1rem;margin-top:.125rem;padding-top:.125rem}.page-links li:not(:first-child){border-top:1px dashed #e9ecef}.page-links a{color:#1d2d35;display:block;padding:.125rem 0;font-size:.9375rem}.docs-link:hover,.docs-link.active,.page-links a:hover{text-decoration:none;color:#5d2f86}.docs-links h3.sidebar-link,.docs-links .sidebar-link.h3,.page-links h3.sidebar-link,.page-links .sidebar-link.h3{text-transform:none;font-size:1.125rem;font-weight:normal}.docs-links h3.sidebar-link a,.docs-links .sidebar-link.h3 a,.page-links h3.sidebar-link a,.page-links .sidebar-link.h3 a{color:#1d2d35}.docs-links h3.sidebar-link a:hover,.docs-links .sidebar-link.h3 a:hover,.page-links h3.sidebar-link a:hover,.page-links .sidebar-link.h3 a:hover{text-decoration:underline}img{width:100%} \ No newline at end of file diff --git a/minibend/001-basic-intro/01-b-tree-disk.png b/minibend/001-basic-intro/01-b-tree-disk.png new file mode 100644 index 0000000..83b9c5c Binary files /dev/null and b/minibend/001-basic-intro/01-b-tree-disk.png differ diff --git a/minibend/001-basic-intro/02-join-reorder.png b/minibend/001-basic-intro/02-join-reorder.png new file mode 100644 index 0000000..27ee449 Binary files /dev/null and b/minibend/001-basic-intro/02-join-reorder.png differ diff --git a/minibend/001-basic-intro/minibend-001-basic-intro_01.png b/minibend/001-basic-intro/minibend-001-basic-intro_01.png new file mode 100644 index 0000000..290afa0 Binary files /dev/null and b/minibend/001-basic-intro/minibend-001-basic-intro_01.png differ diff --git a/minibend/001-basic-intro/minibend-001-basic-intro_12.png b/minibend/001-basic-intro/minibend-001-basic-intro_12.png new file mode 100644 index 0000000..c3f88a8 Binary files /dev/null and b/minibend/001-basic-intro/minibend-001-basic-intro_12.png differ diff --git a/minibend/001-basic-intro/minibend-001-basic-intro_13.png b/minibend/001-basic-intro/minibend-001-basic-intro_13.png new file mode 100644 index 0000000..8d34fd8 Binary files /dev/null and b/minibend/001-basic-intro/minibend-001-basic-intro_13.png differ diff --git a/minibend/001-basic-intro/minibend-001-basic-intro_15.png b/minibend/001-basic-intro/minibend-001-basic-intro_15.png new file mode 100644 index 0000000..225db22 Binary files /dev/null and b/minibend/001-basic-intro/minibend-001-basic-intro_15.png differ diff --git a/minibend/001-basic-intro/minibend-001-basic-intro_22.png b/minibend/001-basic-intro/minibend-001-basic-intro_22.png new file mode 100644 index 0000000..0efaa7f Binary files /dev/null and b/minibend/001-basic-intro/minibend-001-basic-intro_22.png differ diff --git a/minibend/002-datasource/minibend-002_17.png b/minibend/002-datasource/minibend-002_17.png new file mode 100644 index 0000000..88403ff Binary files /dev/null and b/minibend/002-datasource/minibend-002_17.png differ diff --git a/minibend/002-datasource/minibend-002_18.png b/minibend/002-datasource/minibend-002_18.png new file mode 100644 index 0000000..5ff7a27 Binary files /dev/null and b/minibend/002-datasource/minibend-002_18.png differ diff --git a/minibend/002-datasource/minibend-002_19.png b/minibend/002-datasource/minibend-002_19.png new file mode 100644 index 0000000..4c62c6f Binary files /dev/null and b/minibend/002-datasource/minibend-002_19.png differ diff --git a/minibend/002-datasource/minibend-002_20.png b/minibend/002-datasource/minibend-002_20.png new file mode 100644 index 0000000..87eb266 Binary files /dev/null and b/minibend/002-datasource/minibend-002_20.png differ diff --git a/minibend/002-datasource/minibend-002_21.png b/minibend/002-datasource/minibend-002_21.png new file mode 100644 index 0000000..f31687a Binary files /dev/null and b/minibend/002-datasource/minibend-002_21.png differ diff --git a/minibend/002-datasource/minibend-002_22.png b/minibend/002-datasource/minibend-002_22.png new file mode 100644 index 0000000..b9c7032 Binary files /dev/null and b/minibend/002-datasource/minibend-002_22.png differ diff --git a/minibend/002-datasource/minibend-002_23.png b/minibend/002-datasource/minibend-002_23.png new file mode 100644 index 0000000..35f7fe7 Binary files /dev/null and b/minibend/002-datasource/minibend-002_23.png differ diff --git a/minibend/ppt/minibend-001-basic-intro.pdf b/minibend/ppt/minibend-001-basic-intro.pdf new file mode 100644 index 0000000..f07788e Binary files /dev/null and b/minibend/ppt/minibend-001-basic-intro.pdf differ diff --git a/minibend/ppt/minibend-002-datasource.pdf b/minibend/ppt/minibend-002-datasource.pdf new file mode 100644 index 0000000..103eb1e Binary files /dev/null and b/minibend/ppt/minibend-002-datasource.pdf differ diff --git a/plugins/elasticlunr.min.js b/plugins/elasticlunr.min.js new file mode 100644 index 0000000..94b20dd --- /dev/null +++ b/plugins/elasticlunr.min.js @@ -0,0 +1,10 @@ +/** + * elasticlunr - http://weixsong.github.io + * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5 + * + * Copyright (C) 2017 Oliver Nightingale + * Copyright (C) 2017 Wei Song + * MIT Licensed + * @license + */ +!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +隐私政策 | Databend 内幕大揭秘 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+
+
+
+ +

TLDR: 本站不使用 Cookies 且不收集任何个人数据。

+

致网站访问者

+
    +
  • 没有收集个人信息。
  • +
  • 浏览器中不存储任何信息。
  • +
  • 未与第三方共享、发送或出售任何信息。
  • +
  • 不与广告公司共享任何信息。
  • +
  • 没有信息被挖掘和收集,以了解个人和行为趋势。
  • +
  • 没有任何信息被货币化。
  • +
+

联络我们

+

如果你有任何疑问,请随时联络 psiace@datafuselabs.com 进行沟通。

+
+

生效日期: 2022 年 5 月 1 日

+ +
+
+
+
+
+ + + + + + + + + + + + + + diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022.pdf b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022.pdf new file mode 100644 index 0000000..b59314a Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022.pdf differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_01.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_01.png new file mode 100644 index 0000000..7498e11 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_01.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_02.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_02.png new file mode 100644 index 0000000..258d736 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_02.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_03.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_03.png new file mode 100644 index 0000000..35861b8 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_03.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_04.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_04.png new file mode 100644 index 0000000..8d86bbb Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_04.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_05.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_05.png new file mode 100644 index 0000000..a11dd76 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_05.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_06.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_06.png new file mode 100644 index 0000000..8998b58 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_06.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_07.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_07.png new file mode 100644 index 0000000..85732f3 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_07.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_08.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_08.png new file mode 100644 index 0000000..77b4978 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_08.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_09.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_09.png new file mode 100644 index 0000000..11ae756 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_09.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_10.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_10.png new file mode 100644 index 0000000..11e75b6 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_10.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_11.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_11.png new file mode 100644 index 0000000..e37b805 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_11.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_12.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_12.png new file mode 100644 index 0000000..d0877a5 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_12.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_13.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_13.png new file mode 100644 index 0000000..14e556d Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_13.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_14.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_14.png new file mode 100644 index 0000000..2806f31 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_14.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_15.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_15.png new file mode 100644 index 0000000..53bf4c3 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_15.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_16.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_16.png new file mode 100644 index 0000000..cf65f87 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_16.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_17.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_17.png new file mode 100644 index 0000000..6f49c09 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_17.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_18.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_18.png new file mode 100644 index 0000000..958f5e4 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_18.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_19.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_19.png new file mode 100644 index 0000000..693fc4d Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_19.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_20.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_20.png new file mode 100644 index 0000000..e76e3af Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_20.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_21.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_21.png new file mode 100644 index 0000000..51b6ba9 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_21.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_22.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_22.png new file mode 100644 index 0000000..0471d42 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_22.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_23.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_23.png new file mode 100644 index 0000000..615d949 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_23.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_24.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_24.png new file mode 100644 index 0000000..b921ba8 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_24.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_25.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_25.png new file mode 100644 index 0000000..23c3323 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_25.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_26.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_26.png new file mode 100644 index 0000000..4e7775c Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_26.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_27.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_27.png new file mode 100644 index 0000000..33af8fa Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_27.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_28.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_28.png new file mode 100644 index 0000000..d615d5d Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_28.png differ diff --git a/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_29.png b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_29.png new file mode 100644 index 0000000..4618ec7 Binary files /dev/null and b/productivity-topics/Databend-RustConfChina2022/Databend-RustConfChina2022_29.png differ diff --git a/productivity-topics/sky-computing/databend-sky-computing-01.png b/productivity-topics/sky-computing/databend-sky-computing-01.png new file mode 100644 index 0000000..b74ba61 Binary files /dev/null and b/productivity-topics/sky-computing/databend-sky-computing-01.png differ diff --git a/productivity-topics/sky-computing/databend-sky-computing-02.jpg b/productivity-topics/sky-computing/databend-sky-computing-02.jpg new file mode 100644 index 0000000..fb52f7b Binary files /dev/null and b/productivity-topics/sky-computing/databend-sky-computing-02.jpg differ diff --git a/productivity-topics/sky-computing/databend-sky-computing-03.png b/productivity-topics/sky-computing/databend-sky-computing-03.png new file mode 100644 index 0000000..e35652c Binary files /dev/null and b/productivity-topics/sky-computing/databend-sky-computing-03.png differ diff --git a/productivity-topics/sky-computing/databend-sky-computing-04.png b/productivity-topics/sky-computing/databend-sky-computing-04.png new file mode 100644 index 0000000..9e88926 Binary files /dev/null and b/productivity-topics/sky-computing/databend-sky-computing-04.png differ diff --git a/productivity-topics/sky-computing/databend-sky-computing-05.png b/productivity-topics/sky-computing/databend-sky-computing-05.png new file mode 100644 index 0000000..384d251 Binary files /dev/null and b/productivity-topics/sky-computing/databend-sky-computing-05.png differ diff --git a/productivity-topics/sky-computing/databend-sky-computing-06.png b/productivity-topics/sky-computing/databend-sky-computing-06.png new file mode 100644 index 0000000..af68482 Binary files /dev/null and b/productivity-topics/sky-computing/databend-sky-computing-06.png differ diff --git a/productivity-topics/sky-computing/databend-sky-computing-07.png b/productivity-topics/sky-computing/databend-sky-computing-07.png new file mode 100644 index 0000000..d1ed77c Binary files /dev/null and b/productivity-topics/sky-computing/databend-sky-computing-07.png differ diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..6257729 --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://databend-internals.psiace.me/sitemap.xml diff --git a/site.webmanifest b/site.webmanifest new file mode 100644 index 0000000..4ce700f --- /dev/null +++ b/site.webmanifest @@ -0,0 +1 @@ +{"name":"Zola Theme AdiDoks","short_name":"AdiDoks","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#fff","background_color":"#fff","display":"standalone"} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..7ed2696 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,144 @@ + + + + https://databend-internals.psiace.me/ + + + https://databend-internals.psiace.me/authors/ + + + https://databend-internals.psiace.me/docs/ + + + https://databend-internals.psiace.me/docs/advanced-workshops/ + + + https://databend-internals.psiace.me/docs/advanced-workshops/paper-list/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/build-and-more/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/build-with-pgo/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/development-environment/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/faq/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/how-to-benchmark/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/how-to-contribute/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/how-to-profile/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/how-to-write-functions/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/how-to-write-system-table/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/release/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/routine-maintenance/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/tracing-in-databend/ + + + https://databend-internals.psiace.me/docs/contribute-to-databend/write-and-run-tests/ + + + https://databend-internals.psiace.me/docs/contributing/ + + + https://databend-internals.psiace.me/docs/contributing/code-of-conduct/ + + + https://databend-internals.psiace.me/docs/contributing/how-to-contributing/ + + + https://databend-internals.psiace.me/docs/contributing/outline/ + + + https://databend-internals.psiace.me/docs/getting-started/ + + + https://databend-internals.psiace.me/docs/getting-started/introduction/ + + + https://databend-internals.psiace.me/docs/minibend/ + + + https://databend-internals.psiace.me/docs/minibend/datasource/ + + + https://databend-internals.psiace.me/docs/minibend/intro/ + + + https://databend-internals.psiace.me/docs/productivity-topics/ + + + https://databend-internals.psiace.me/docs/productivity-topics/databend-rustchinaconf/ + + + https://databend-internals.psiace.me/docs/productivity-topics/quality-assurance-in-databend/ + + + https://databend-internals.psiace.me/docs/productivity-topics/the-databend-way-to-sky/ + + + https://databend-internals.psiace.me/docs/source-reading/ + + + https://databend-internals.psiace.me/docs/source-reading/config/ + + + https://databend-internals.psiace.me/docs/source-reading/init-session-handler/ + + + https://databend-internals.psiace.me/docs/source-reading/intro/ + + + https://databend-internals.psiace.me/docs/source-reading/pipeline-execution/ + + + https://databend-internals.psiace.me/docs/source-reading/pipeline-model-graph-based/ + + + https://databend-internals.psiace.me/docs/source-reading/storage-basics-read-partitions/ + + + https://databend-internals.psiace.me/docs/the-basics/ + + + https://databend-internals.psiace.me/docs/the-basics/about-index/ + + + https://databend-internals.psiace.me/docs/the-basics/distributed/ + + + https://databend-internals.psiace.me/docs/the-basics/executor-in-query-process/ + + + https://databend-internals.psiace.me/docs/the-basics/mpp/ + + + https://databend-internals.psiace.me/docs/the-basics/query-optimization/ + + + https://databend-internals.psiace.me/docs/the-basics/storage/ + + + https://databend-internals.psiace.me/privacy-policy/ + + diff --git a/source-reading/init-session-handler/01-query-server.png b/source-reading/init-session-handler/01-query-server.png new file mode 100644 index 0000000..ae97f09 Binary files /dev/null and b/source-reading/init-session-handler/01-query-server.png differ diff --git a/source-reading/init-session-handler/02-session.png b/source-reading/init-session-handler/02-session.png new file mode 100644 index 0000000..3434488 Binary files /dev/null and b/source-reading/init-session-handler/02-session.png differ diff --git a/source-reading/intro/01-vectorsql.png b/source-reading/intro/01-vectorsql.png new file mode 100644 index 0000000..46e8583 Binary files /dev/null and b/source-reading/intro/01-vectorsql.png differ diff --git a/source-reading/intro/02-databend-arch.png b/source-reading/intro/02-databend-arch.png new file mode 100644 index 0000000..7adfe21 Binary files /dev/null and b/source-reading/intro/02-databend-arch.png differ diff --git a/source-reading/intro/03-query.png b/source-reading/intro/03-query.png new file mode 100644 index 0000000..5bcdc19 Binary files /dev/null and b/source-reading/intro/03-query.png differ diff --git a/source-reading/pipeline_model_graph/1-pipeline-arch.png b/source-reading/pipeline_model_graph/1-pipeline-arch.png new file mode 100644 index 0000000..4959033 Binary files /dev/null and b/source-reading/pipeline_model_graph/1-pipeline-arch.png differ diff --git a/source-reading/pipeline_model_graph/2-pipeline-graph-build-01.jpg b/source-reading/pipeline_model_graph/2-pipeline-graph-build-01.jpg new file mode 100644 index 0000000..57adc71 Binary files /dev/null and b/source-reading/pipeline_model_graph/2-pipeline-graph-build-01.jpg differ diff --git a/source-reading/pipeline_model_graph/3-pipeline-graph-build-02.jpg b/source-reading/pipeline_model_graph/3-pipeline-graph-build-02.jpg new file mode 100644 index 0000000..2b74fee Binary files /dev/null and b/source-reading/pipeline_model_graph/3-pipeline-graph-build-02.jpg differ diff --git a/source-reading/pipeline_model_graph/4-pipeline-graph-build-03.jpg b/source-reading/pipeline_model_graph/4-pipeline-graph-build-03.jpg new file mode 100644 index 0000000..17312aa Binary files /dev/null and b/source-reading/pipeline_model_graph/4-pipeline-graph-build-03.jpg differ diff --git a/source-reading/pipeline_model_graph/5-pipeline-model.jpg b/source-reading/pipeline_model_graph/5-pipeline-model.jpg new file mode 100644 index 0000000..34ca7d7 Binary files /dev/null and b/source-reading/pipeline_model_graph/5-pipeline-model.jpg differ diff --git a/source-reading/pipeline_model_graph/6-parallel-pipeline-model.jpg b/source-reading/pipeline_model_graph/6-parallel-pipeline-model.jpg new file mode 100644 index 0000000..5920237 Binary files /dev/null and b/source-reading/pipeline_model_graph/6-parallel-pipeline-model.jpg differ diff --git a/the-basics/executor-in-query-process/01-query-steps.png b/the-basics/executor-in-query-process/01-query-steps.png new file mode 100644 index 0000000..2633399 Binary files /dev/null and b/the-basics/executor-in-query-process/01-query-steps.png differ diff --git a/the-basics/executor-in-query-process/02-volcano.png b/the-basics/executor-in-query-process/02-volcano.png new file mode 100644 index 0000000..7b56113 Binary files /dev/null and b/the-basics/executor-in-query-process/02-volcano.png differ diff --git a/the-basics/executor-in-query-process/03-morsel-driven.png b/the-basics/executor-in-query-process/03-morsel-driven.png new file mode 100644 index 0000000..00a323e Binary files /dev/null and b/the-basics/executor-in-query-process/03-morsel-driven.png differ diff --git a/the-basics/executor-in-query-process/04-column-based-vectorwise.png b/the-basics/executor-in-query-process/04-column-based-vectorwise.png new file mode 100644 index 0000000..3850e05 Binary files /dev/null and b/the-basics/executor-in-query-process/04-column-based-vectorwise.png differ diff --git a/the-basics/storage/01-storage-level.png b/the-basics/storage/01-storage-level.png new file mode 100644 index 0000000..ecebf05 Binary files /dev/null and b/the-basics/storage/01-storage-level.png differ