├── .github
└── FUNDING.yml
├── .gitignore
├── DB DICTIONARY Adventureworks.md
├── DB DICTIONARY dvdrental.md
├── DB DICTIONARY northwind.md
├── LICENSE
├── README.md
├── _config.yml
├── build.bat
├── build.sh
└── src
├── Markup.cs
├── PgComment.csproj
├── PgComment.sln
├── Program.cs
├── Settings.cs
├── Sql.cs
├── Update.cs
└── settings.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 | /src/settings.private.json
352 | out/
353 |
--------------------------------------------------------------------------------
/DB DICTIONARY dvdrental.md:
--------------------------------------------------------------------------------
1 | # Dictionary for database `dvdrental`
2 |
3 | - Server: PostgreSQL `localhost:5432`, version `10.4`
4 | - Local timestamp: `2020-02-13T12:10:52.7721381+01:00`
5 | - Schemas: `public`
6 |
7 | ## Table of Contents
8 |
9 | - Table [`public.actor`](#table-publicactor)
10 | - Table [`public.address`](#table-publicaddress)
11 | - Table [`public.category`](#table-publiccategory)
12 | - Table [`public.city`](#table-publiccity)
13 | - Table [`public.country`](#table-publiccountry)
14 | - Table [`public.customer`](#table-publiccustomer)
15 | - Table [`public.film`](#table-publicfilm)
16 | - Table [`public.film_actor`](#table-publicfilm_actor)
17 | - Table [`public.film_category`](#table-publicfilm_category)
18 | - Table [`public.inventory`](#table-publicinventory)
19 | - Table [`public.language`](#table-publiclanguage)
20 | - Table [`public.payment`](#table-publicpayment)
21 | - Table [`public.rental`](#table-publicrental)
22 | - Table [`public.staff`](#table-publicstaff)
23 | - Table [`public.store`](#table-publicstore)
24 | - View [`public.actor_info`](#view-publicactor_info)
25 | - View [`public.customer_list`](#view-publiccustomer_list)
26 | - View [`public.film_list`](#view-publicfilm_list)
27 | - View [`public.nicer_but_slower_film_list`](#view-publicnicer_but_slower_film_list)
28 | - View [`public.sales_by_film_category`](#view-publicsales_by_film_category)
29 | - View [`public.sales_by_store`](#view-publicsales_by_store)
30 | - View [`public.staff_list`](#view-publicstaff_list)
31 | - Function [`public._group_concat(text, text)`](#function-public_group_concattext-text)
32 | - Function [`public.film_in_stock(integer, integer, out integer)`](#function-publicfilm_in_stockinteger-integer-out-integer)
33 | - Function [`public.film_not_in_stock(integer, integer, out integer)`](#function-publicfilm_not_in_stockinteger-integer-out-integer)
34 | - Function [`public.get_customer_balance(integer, timestamp without time zone)`](#function-publicget_customer_balanceinteger-timestamp-without-time-zone)
35 | - Function [`public.inventory_held_by_customer(integer)`](#function-publicinventory_held_by_customerinteger)
36 | - Function [`public.inventory_in_stock(integer)`](#function-publicinventory_in_stockinteger)
37 | - Function [`public.last_day(timestamp without time zone)`](#function-publiclast_daytimestamp-without-time-zone)
38 | - Function [`public.last_updated()`](#function-publiclast_updated)
39 | - Function [`public.rewards_report(integer, numeric)`](#function-publicrewards_reportinteger-numeric)
40 | - Function [`public.test()`](#function-publictest)
41 |
42 | ## Tables
43 |
44 | ### Table `public.actor`
45 |
46 |
47 | test comment on actor
48 |
49 |
50 | | Column | | Type | Nullable | Default | Comment |
51 | | ------ | ----------- | -----| -------- | ------- | ------- |
52 | | #`actor_id` | **PK** | `integer` | **NO** | *auto increment* | |
53 | | #`first_name` | | `character varying(45)` | **NO** | | test comment on actor.first_name field |
54 | | #`last_name` | **IDX** | `character varying(45)` | **NO** | | |
55 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
56 |
57 | ⇡
58 |
59 | ### Table `public.address`
60 |
61 |
62 |
63 |
64 | | Column | | Type | Nullable | Default | Comment |
65 | | ------ | ----------- | -----| -------- | ------- | ------- |
66 | | #`address_id` | **PK** | `integer` | **NO** | *auto increment* | |
67 | | #`address` | | `character varying(50)` | **NO** | | |
68 | | #`address2` | | `character varying(50)` | YES | | |
69 | | #`district` | | `character varying(20)` | **NO** | | |
70 | | #`city_id` | **FK [➝](#public-city-city_id) `city.city_id`**, **IDX** | `smallint` | **NO** | | |
71 | | #`postal_code` | | `character varying(10)` | YES | | |
72 | | #`phone` | | `character varying(20)` | **NO** | | |
73 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
74 |
75 | ⇡
76 |
77 | ### Table `public.category`
78 |
79 |
80 |
81 |
82 | | Column | | Type | Nullable | Default | Comment |
83 | | ------ | ----------- | -----| -------- | ------- | ------- |
84 | | #`category_id` | **PK** | `integer` | **NO** | *auto increment* | |
85 | | #`name` | | `character varying(25)` | **NO** | | |
86 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
87 |
88 | ⇡
89 |
90 | ### Table `public.city`
91 |
92 |
93 |
94 |
95 | | Column | | Type | Nullable | Default | Comment |
96 | | ------ | ----------- | -----| -------- | ------- | ------- |
97 | | #`city_id` | **PK** | `integer` | **NO** | *auto increment* | |
98 | | #`city` | | `character varying(50)` | **NO** | | |
99 | | #`country_id` | **FK [➝](#public-country-country_id) `country.country_id`**, **IDX** | `smallint` | **NO** | | |
100 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
101 |
102 | ⇡
103 |
104 | ### Table `public.country`
105 |
106 |
107 |
108 |
109 | | Column | | Type | Nullable | Default | Comment |
110 | | ------ | ----------- | -----| -------- | ------- | ------- |
111 | | #`country_id` | **PK** | `integer` | **NO** | *auto increment* | |
112 | | #`country` | | `character varying(50)` | **NO** | | |
113 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
114 |
115 | ⇡
116 |
117 | ### Table `public.customer`
118 |
119 |
120 |
121 |
122 | | Column | | Type | Nullable | Default | Comment |
123 | | ------ | ----------- | -----| -------- | ------- | ------- |
124 | | #`customer_id` | **PK** | `integer` | **NO** | *auto increment* | |
125 | | #`store_id` | **IDX** | `smallint` | **NO** | | |
126 | | #`first_name` | | `character varying(45)` | **NO** | | |
127 | | #`last_name` | **IDX** | `character varying(45)` | **NO** | | |
128 | | #`email` | | `character varying(50)` | YES | | |
129 | | #`address_id` | **FK [➝](#public-address-address_id) `address.address_id`**, **IDX** | `smallint` | **NO** | | |
130 | | #`activebool` | | `boolean` | **NO** | `true` | |
131 | | #`create_date` | | `date` | **NO** | `('now'::text)::date` | |
132 | | #`last_update` | | `timestamp without time zone` | YES | `now()` | |
133 | | #`active` | | `integer` | YES | | |
134 |
135 | ⇡
136 |
137 | ### Table `public.film`
138 |
139 |
140 |
141 |
142 | | Column | | Type | Nullable | Default | Comment |
143 | | ------ | ----------- | -----| -------- | ------- | ------- |
144 | | #`film_id` | **PK** | `integer` | **NO** | *auto increment* | |
145 | | #`title` | **IDX** | `character varying(255)` | **NO** | | |
146 | | #`description` | | `text` | YES | | |
147 | | #`release_year` | | `integer` | YES | | |
148 | | #`language_id` | **FK [➝](#public-language-language_id) `language.language_id`**, **IDX** | `smallint` | **NO** | | |
149 | | #`rental_duration` | | `smallint` | **NO** | `3` | |
150 | | #`rental_rate` | | `numeric(4,10),2)` | **NO** | `4.99` | |
151 | | #`length` | | `smallint` | YES | | |
152 | | #`replacement_cost` | | `numeric(5,10),2)` | **NO** | `19.99` | |
153 | | #`rating` | | `USER-DEFINED` | YES | `'G'::mpaa_rating` | |
154 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
155 | | #`special_features` | | `ARRAY` | YES | | |
156 | | #`fulltext` | **IDX** | `tsvector` | **NO** | | |
157 |
158 | ⇡
159 |
160 | ### Table `public.film_actor`
161 |
162 |
163 |
164 |
165 | | Column | | Type | Nullable | Default | Comment |
166 | | ------ | ----------- | -----| -------- | ------- | ------- |
167 | | #`actor_id` | **PK**, **FK [➝](#public-actor-actor_id) `actor.actor_id`** | `smallint` | **NO** | | |
168 | | #`film_id` | **PK**, **FK [➝](#public-film-film_id) `film.film_id`**, **IDX** | `smallint` | **NO** | | |
169 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
170 |
171 | ⇡
172 |
173 | ### Table `public.film_category`
174 |
175 |
176 |
177 |
178 | | Column | | Type | Nullable | Default | Comment |
179 | | ------ | ----------- | -----| -------- | ------- | ------- |
180 | | #`film_id` | **PK**, **FK [➝](#public-film-film_id) `film.film_id`** | `smallint` | **NO** | | |
181 | | #`category_id` | **PK**, **FK [➝](#public-category-category_id) `category.category_id`** | `smallint` | **NO** | | |
182 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
183 |
184 | ⇡
185 |
186 | ### Table `public.inventory`
187 |
188 |
189 |
190 |
191 | | Column | | Type | Nullable | Default | Comment |
192 | | ------ | ----------- | -----| -------- | ------- | ------- |
193 | | #`inventory_id` | **PK** | `integer` | **NO** | *auto increment* | |
194 | | #`film_id` | **FK [➝](#public-film-film_id) `film.film_id`**, **IDX** | `smallint` | **NO** | | |
195 | | #`store_id` | **IDX** | `smallint` | **NO** | | |
196 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
197 |
198 | ⇡
199 |
200 | ### Table `public.language`
201 |
202 |
203 |
204 |
205 | | Column | | Type | Nullable | Default | Comment |
206 | | ------ | ----------- | -----| -------- | ------- | ------- |
207 | | #`language_id` | **PK** | `integer` | **NO** | *auto increment* | |
208 | | #`name` | | `character(20)` | **NO** | | |
209 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
210 |
211 | ⇡
212 |
213 | ### Table `public.payment`
214 |
215 |
216 |
217 |
218 | | Column | | Type | Nullable | Default | Comment |
219 | | ------ | ----------- | -----| -------- | ------- | ------- |
220 | | #`payment_id` | **PK** | `integer` | **NO** | *auto increment* | |
221 | | #`customer_id` | **FK [➝](#public-customer-customer_id) `customer.customer_id`**, **IDX** | `smallint` | **NO** | | |
222 | | #`staff_id` | **FK [➝](#public-staff-staff_id) `staff.staff_id`**, **IDX** | `smallint` | **NO** | | |
223 | | #`rental_id` | **FK [➝](#public-rental-rental_id) `rental.rental_id`**, **IDX** | `integer` | **NO** | | |
224 | | #`amount` | | `numeric(5,10),2)` | **NO** | | |
225 | | #`payment_date` | | `timestamp without time zone` | **NO** | | |
226 |
227 | ⇡
228 |
229 | ### Table `public.rental`
230 |
231 |
232 |
233 |
234 | | Column | | Type | Nullable | Default | Comment |
235 | | ------ | ----------- | -----| -------- | ------- | ------- |
236 | | #`rental_id` | **PK** | `integer` | **NO** | *auto increment* | |
237 | | #`rental_date` | **IDX** | `timestamp without time zone` | **NO** | | |
238 | | #`inventory_id` | **FK [➝](#public-inventory-inventory_id) `inventory.inventory_id`**, **IDX**, **IDX** | `integer` | **NO** | | |
239 | | #`customer_id` | **FK [➝](#public-customer-customer_id) `customer.customer_id`**, **IDX** | `smallint` | **NO** | | |
240 | | #`return_date` | | `timestamp without time zone` | YES | | |
241 | | #`staff_id` | **FK [➝](#public-staff-staff_id) `staff.staff_id`** | `smallint` | **NO** | | |
242 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
243 |
244 | ⇡
245 |
246 | ### Table `public.staff`
247 |
248 |
249 |
250 |
251 | | Column | | Type | Nullable | Default | Comment |
252 | | ------ | ----------- | -----| -------- | ------- | ------- |
253 | | #`staff_id` | **PK** | `integer` | **NO** | *auto increment* | |
254 | | #`first_name` | | `character varying(45)` | **NO** | | |
255 | | #`last_name` | | `character varying(45)` | **NO** | | |
256 | | #`address_id` | **FK [➝](#public-address-address_id) `address.address_id`** | `smallint` | **NO** | | |
257 | | #`email` | | `character varying(50)` | YES | | |
258 | | #`store_id` | | `smallint` | **NO** | | |
259 | | #`active` | | `boolean` | **NO** | `true` | |
260 | | #`username` | | `character varying(16)` | **NO** | | |
261 | | #`password` | | `character varying(40)` | YES | | |
262 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
263 | | #`picture` | | `bytea` | YES | | |
264 |
265 | ⇡
266 |
267 | ### Table `public.store`
268 |
269 |
270 |
271 |
272 | | Column | | Type | Nullable | Default | Comment |
273 | | ------ | ----------- | -----| -------- | ------- | ------- |
274 | | #`store_id` | **PK** | `integer` | **NO** | *auto increment* | |
275 | | #`manager_staff_id` | **FK [➝](#public-staff-staff_id) `staff.staff_id`**, **IDX** | `smallint` | **NO** | | |
276 | | #`address_id` | **FK [➝](#public-address-address_id) `address.address_id`** | `smallint` | **NO** | | |
277 | | #`last_update` | | `timestamp without time zone` | **NO** | `now()` | |
278 |
279 | ⇡
280 |
281 | ## Views
282 |
283 | ⇡
284 |
285 | ### View `public.actor_info`
286 |
287 |
288 |
289 |
290 | | Column | Type | Comment |
291 | | ------ | ---- | --------|
292 | | `actor_id` | `integer` | |
293 | | `first_name` | `character varying(45)` | |
294 | | `last_name` | `character varying(45)` | |
295 | | `film_info` | `text` | |
296 |
297 | ⇡
298 |
299 | ### View `public.customer_list`
300 |
301 |
302 |
303 |
304 | | Column | Type | Comment |
305 | | ------ | ---- | --------|
306 | | `id` | `integer` | |
307 | | `name` | `text` | |
308 | | `address` | `character varying(50)` | |
309 | | `zip code` | `character varying(10)` | |
310 | | `phone` | `character varying(20)` | |
311 | | `city` | `character varying(50)` | |
312 | | `country` | `character varying(50)` | |
313 | | `notes` | `text` | |
314 | | `sid` | `smallint` | |
315 |
316 | ⇡
317 |
318 | ### View `public.film_list`
319 |
320 |
321 |
322 |
323 | | Column | Type | Comment |
324 | | ------ | ---- | --------|
325 | | `fid` | `integer` | |
326 | | `title` | `character varying(255)` | |
327 | | `description` | `text` | |
328 | | `category` | `character varying(25)` | |
329 | | `price` | `numeric(4,10),2)` | |
330 | | `length` | `smallint` | |
331 | | `rating` | `USER-DEFINED` | |
332 | | `actors` | `text` | |
333 |
334 | ⇡
335 |
336 | ### View `public.nicer_but_slower_film_list`
337 |
338 |
339 |
340 |
341 | | Column | Type | Comment |
342 | | ------ | ---- | --------|
343 | | `fid` | `integer` | |
344 | | `title` | `character varying(255)` | |
345 | | `description` | `text` | |
346 | | `category` | `character varying(25)` | |
347 | | `price` | `numeric(4,10),2)` | |
348 | | `length` | `smallint` | |
349 | | `rating` | `USER-DEFINED` | |
350 | | `actors` | `text` | |
351 |
352 | ⇡
353 |
354 | ### View `public.sales_by_film_category`
355 |
356 |
357 |
358 |
359 | | Column | Type | Comment |
360 | | ------ | ---- | --------|
361 | | `category` | `character varying(25)` | |
362 | | `total_sales` | `numeric` | |
363 |
364 | ⇡
365 |
366 | ### View `public.sales_by_store`
367 |
368 |
369 |
370 |
371 | | Column | Type | Comment |
372 | | ------ | ---- | --------|
373 | | `store` | `text` | |
374 | | `manager` | `text` | |
375 | | `total_sales` | `numeric` | |
376 |
377 | ⇡
378 |
379 | ### View `public.staff_list`
380 |
381 |
382 |
383 |
384 | | Column | Type | Comment |
385 | | ------ | ---- | --------|
386 | | `id` | `integer` | |
387 | | `name` | `text` | |
388 | | `address` | `character varying(50)` | |
389 | | `zip code` | `character varying(10)` | |
390 | | `phone` | `character varying(20)` | |
391 | | `city` | `character varying(50)` | |
392 | | `country` | `character varying(50)` | |
393 | | `sid` | `smallint` | |
394 |
395 | ⇡
396 |
397 | ## Routines
398 |
399 | ### Function `public._group_concat(text, text)`
400 |
401 | - Returns `text`
402 |
403 | - Language is `sql`
404 |
405 |
406 | comment for _group_concat
407 |
408 |
409 | ⇡
410 |
411 | ### Function `public.film_in_stock(integer, integer, out integer)`
412 |
413 | - Returns `integer`
414 |
415 | - Language is `sql`
416 |
417 |
418 |
419 |
420 | ⇡
421 |
422 | ### Function `public.film_not_in_stock(integer, integer, out integer)`
423 |
424 | - Returns `integer`
425 |
426 | - Language is `sql`
427 |
428 |
429 |
430 |
431 | ⇡
432 |
433 | ### Function `public.get_customer_balance(integer, timestamp without time zone)`
434 |
435 | - Returns `numeric`
436 |
437 | - Language is `plpgsql`
438 |
439 |
440 |
441 |
442 | ⇡
443 |
444 | ### Function `public.inventory_held_by_customer(integer)`
445 |
446 | - Returns `integer`
447 |
448 | - Language is `plpgsql`
449 |
450 |
451 |
452 |
453 | ⇡
454 |
455 | ### Function `public.inventory_in_stock(integer)`
456 |
457 | - Returns `boolean`
458 |
459 | - Language is `plpgsql`
460 |
461 |
462 |
463 |
464 | ⇡
465 |
466 | ### Function `public.last_day(timestamp without time zone)`
467 |
468 | - Returns `date`
469 |
470 | - Language is `sql`
471 |
472 |
473 |
474 |
475 | ⇡
476 |
477 | ### Function `public.last_updated()`
478 |
479 | - Returns `trigger`
480 |
481 | - Language is `plpgsql`
482 |
483 |
484 |
485 |
486 | ⇡
487 |
488 | ### Function `public.rewards_report(integer, numeric)`
489 |
490 | - Returns `setof customer`
491 |
492 | - Language is `plpgsql`
493 |
494 |
495 |
496 |
497 | ⇡
498 |
499 | ### Function `public.test()`
500 |
501 | - Returns `void`
502 |
503 | - Language is `plpgsql`
504 |
505 |
506 |
507 |
508 | ⇡
509 |
510 |
--------------------------------------------------------------------------------
/DB DICTIONARY northwind.md:
--------------------------------------------------------------------------------
1 | # Dictionary for database `northwind`
2 |
3 | - Server: PostgreSQL `localhost:5434`, version `12.0`
4 | - Local timestamp: `2020-02-12T12:30:57.7961930+01:00`
5 | - Schemas: `public`
6 |
7 | ## Table of Contents
8 |
9 | - Table [`public.categories`](#table-publiccategories)
10 | - Table [`public.customer_customer_demo`](#table-publiccustomer_customer_demo)
11 | - Table [`public.customer_demographics`](#table-publiccustomer_demographics)
12 | - Table [`public.customers`](#table-publiccustomers)
13 | - Table [`public.employee_territories`](#table-publicemployee_territories)
14 | - Table [`public.employees`](#table-publicemployees)
15 | - Table [`public.order_details`](#table-publicorder_details)
16 | - Table [`public.orders`](#table-publicorders)
17 | - Table [`public.products`](#table-publicproducts)
18 | - Table [`public.region`](#table-publicregion)
19 | - Table [`public.shippers`](#table-publicshippers)
20 | - Table [`public.suppliers`](#table-publicsuppliers)
21 | - Table [`public.territories`](#table-publicterritories)
22 | - Table [`public.us_states`](#table-publicus_states)
23 |
24 | ## Tables
25 |
26 | ### Table `public.categories`
27 |
28 |
29 | Table of categories. Here you can find all the categories.
30 |
31 |
32 | | Column | | Type | Nullable | Default | Comment |
33 | | ------ | ----------- | -----| -------- | ------- | ------- |
34 | | #`category_id` | **PK** | `smallint` | **NO** | | Primary key for table categories |
35 | | #`category_name` | | `character varying(15)` | **NO** | | Name of the category |
36 | | #`description` | | `text` | YES | | Category description. |
37 | | #`picture` | | `bytea` | YES | | Binary content of category picture |
38 |
39 | ⇡
40 |
41 | ### Table `public.customer_customer_demo`
42 |
43 |
44 |
45 |
46 | | Column | | Type | Nullable | Default | Comment |
47 | | ------ | ----------- | -----| -------- | ------- | ------- |
48 | | #`customer_id` | **PK**, **FK [➝](#public-customers-customer_id) `customers.customer_id`** | `character` | **NO** | | |
49 | | #`customer_type_id` | **PK**, **FK [➝](#public-customer_demographics-customer_type_id) `customer_demographics.customer_type_id`** | `character` | **NO** | | |
50 |
51 | ⇡
52 |
53 | ### Table `public.customer_demographics`
54 |
55 |
56 |
57 |
58 | | Column | | Type | Nullable | Default | Comment |
59 | | ------ | ----------- | -----| -------- | ------- | ------- |
60 | | #`customer_type_id` | **PK** | `character` | **NO** | | |
61 | | #`customer_desc` | | `text` | YES | | |
62 |
63 | ⇡
64 |
65 | ### Table `public.customers`
66 |
67 |
68 |
69 |
70 | | Column | | Type | Nullable | Default | Comment |
71 | | ------ | ----------- | -----| -------- | ------- | ------- |
72 | | #`customer_id` | **PK** | `character` | **NO** | | |
73 | | #`company_name` | | `character varying(40)` | **NO** | | |
74 | | #`contact_name` | | `character varying(30)` | YES | | |
75 | | #`contact_title` | | `character varying(30)` | YES | | |
76 | | #`address` | | `character varying(60)` | YES | | |
77 | | #`city` | | `character varying(15)` | YES | | |
78 | | #`region` | | `character varying(15)` | YES | | |
79 | | #`postal_code` | | `character varying(10)` | YES | | |
80 | | #`country` | | `character varying(15)` | YES | | |
81 | | #`phone` | | `character varying(24)` | YES | | |
82 | | #`fax` | | `character varying(24)` | YES | | |
83 |
84 | ⇡
85 |
86 | ### Table `public.employee_territories`
87 |
88 |
89 |
90 |
91 | | Column | | Type | Nullable | Default | Comment |
92 | | ------ | ----------- | -----| -------- | ------- | ------- |
93 | | #`employee_id` | **PK**, **FK [➝](#public-employees-employee_id) `employees.employee_id`** | `smallint` | **NO** | | |
94 | | #`territory_id` | **PK**, **FK [➝](#public-territories-territory_id) `territories.territory_id`** | `character varying(20)` | **NO** | | |
95 |
96 | ⇡
97 |
98 | ### Table `public.employees`
99 |
100 |
101 |
102 |
103 | | Column | | Type | Nullable | Default | Comment |
104 | | ------ | ----------- | -----| -------- | ------- | ------- |
105 | | #`employee_id` | **PK** | `smallint` | **NO** | | |
106 | | #`last_name` | | `character varying(20)` | **NO** | | |
107 | | #`first_name` | | `character varying(10)` | **NO** | | |
108 | | #`title` | | `character varying(30)` | YES | | |
109 | | #`title_of_courtesy` | | `character varying(25)` | YES | | |
110 | | #`birth_date` | | `date` | YES | | |
111 | | #`hire_date` | | `date` | YES | | |
112 | | #`address` | | `character varying(60)` | YES | | |
113 | | #`city` | | `character varying(15)` | YES | | |
114 | | #`region` | | `character varying(15)` | YES | | |
115 | | #`postal_code` | | `character varying(10)` | YES | | |
116 | | #`country` | | `character varying(15)` | YES | | |
117 | | #`home_phone` | | `character varying(24)` | YES | | |
118 | | #`extension` | | `character varying(4)` | YES | | |
119 | | #`photo` | | `bytea` | YES | | |
120 | | #`notes` | | `text` | YES | | |
121 | | #`reports_to` | **FK [➝](#public-employees-employee_id) `employees.employee_id`** | `smallint` | YES | | |
122 | | #`photo_path` | | `character varying(255)` | YES | | |
123 |
124 | ⇡
125 |
126 | ### Table `public.order_details`
127 |
128 |
129 |
130 |
131 | | Column | | Type | Nullable | Default | Comment |
132 | | ------ | ----------- | -----| -------- | ------- | ------- |
133 | | #`order_id` | **PK**, **FK [➝](#public-orders-order_id) `orders.order_id`** | `smallint` | **NO** | | |
134 | | #`product_id` | **PK**, **FK [➝](#public-products-product_id) `products.product_id`** | `smallint` | **NO** | | |
135 | | #`unit_price` | | `real(24,2)` | **NO** | | |
136 | | #`quantity` | | `smallint` | **NO** | | |
137 | | #`discount` | | `real(24,2)` | **NO** | | |
138 |
139 | ⇡
140 |
141 | ### Table `public.orders`
142 |
143 |
144 |
145 |
146 | | Column | | Type | Nullable | Default | Comment |
147 | | ------ | ----------- | -----| -------- | ------- | ------- |
148 | | #`order_id` | **PK** | `smallint` | **NO** | | |
149 | | #`customer_id` | **FK [➝](#public-customers-customer_id) `customers.customer_id`** | `character` | YES | | |
150 | | #`employee_id` | **FK [➝](#public-employees-employee_id) `employees.employee_id`** | `smallint` | YES | | |
151 | | #`order_date` | | `date` | YES | | |
152 | | #`required_date` | | `date` | YES | | |
153 | | #`shipped_date` | | `date` | YES | | |
154 | | #`ship_via` | **FK [➝](#public-shippers-shipper_id) `shippers.shipper_id`** | `smallint` | YES | | |
155 | | #`freight` | | `real(24,2)` | YES | | |
156 | | #`ship_name` | | `character varying(40)` | YES | | |
157 | | #`ship_address` | | `character varying(60)` | YES | | |
158 | | #`ship_city` | | `character varying(15)` | YES | | |
159 | | #`ship_region` | | `character varying(15)` | YES | | |
160 | | #`ship_postal_code` | | `character varying(10)` | YES | | |
161 | | #`ship_country` | | `character varying(15)` | YES | | |
162 |
163 | ⇡
164 |
165 | ### Table `public.products`
166 |
167 |
168 |
169 |
170 | | Column | | Type | Nullable | Default | Comment |
171 | | ------ | ----------- | -----| -------- | ------- | ------- |
172 | | #`product_id` | **PK** | `smallint` | **NO** | | |
173 | | #`product_name` | | `character varying(40)` | **NO** | | |
174 | | #`supplier_id` | **FK [➝](#public-suppliers-supplier_id) `suppliers.supplier_id`** | `smallint` | YES | | |
175 | | #`category_id` | **FK [➝](#public-categories-category_id) `categories.category_id`** | `smallint` | YES | | |
176 | | #`quantity_per_unit` | | `character varying(20)` | YES | | |
177 | | #`unit_price` | | `real(24,2)` | YES | | |
178 | | #`units_in_stock` | | `smallint` | YES | | |
179 | | #`units_on_order` | | `smallint` | YES | | |
180 | | #`reorder_level` | | `smallint` | YES | | |
181 | | #`discontinued` | | `integer` | **NO** | | |
182 |
183 | ⇡
184 |
185 | ### Table `public.region`
186 |
187 |
188 |
189 |
190 | | Column | | Type | Nullable | Default | Comment |
191 | | ------ | ----------- | -----| -------- | ------- | ------- |
192 | | #`region_id` | **PK** | `smallint` | **NO** | | |
193 | | #`region_description` | | `character` | **NO** | | |
194 |
195 | ⇡
196 |
197 | ### Table `public.shippers`
198 |
199 |
200 |
201 |
202 | | Column | | Type | Nullable | Default | Comment |
203 | | ------ | ----------- | -----| -------- | ------- | ------- |
204 | | #`shipper_id` | **PK** | `smallint` | **NO** | | |
205 | | #`company_name` | | `character varying(40)` | **NO** | | |
206 | | #`phone` | | `character varying(24)` | YES | | |
207 |
208 | ⇡
209 |
210 | ### Table `public.suppliers`
211 |
212 |
213 |
214 |
215 | | Column | | Type | Nullable | Default | Comment |
216 | | ------ | ----------- | -----| -------- | ------- | ------- |
217 | | #`supplier_id` | **PK** | `smallint` | **NO** | | |
218 | | #`company_name` | | `character varying(40)` | **NO** | | |
219 | | #`contact_name` | | `character varying(30)` | YES | | |
220 | | #`contact_title` | | `character varying(30)` | YES | | |
221 | | #`address` | | `character varying(60)` | YES | | |
222 | | #`city` | | `character varying(15)` | YES | | |
223 | | #`region` | | `character varying(15)` | YES | | |
224 | | #`postal_code` | | `character varying(10)` | YES | | |
225 | | #`country` | | `character varying(15)` | YES | | |
226 | | #`phone` | | `character varying(24)` | YES | | |
227 | | #`fax` | | `character varying(24)` | YES | | |
228 | | #`homepage` | | `text` | YES | | |
229 |
230 | ⇡
231 |
232 | ### Table `public.territories`
233 |
234 |
235 |
236 |
237 | | Column | | Type | Nullable | Default | Comment |
238 | | ------ | ----------- | -----| -------- | ------- | ------- |
239 | | #`territory_id` | **PK** | `character varying(20)` | **NO** | | |
240 | | #`territory_description` | | `character` | **NO** | | |
241 | | #`region_id` | **FK [➝](#public-region-region_id) `region.region_id`** | `smallint` | **NO** | | |
242 |
243 | ⇡
244 |
245 | ### Table `public.us_states`
246 |
247 |
248 |
249 |
250 | | Column | | Type | Nullable | Default | Comment |
251 | | ------ | ----------- | -----| -------- | ------- | ------- |
252 | | #`state_id` | **PK** | `smallint` | **NO** | | |
253 | | #`state_name` | | `character varying(100)` | YES | | |
254 | | #`state_abbr` | | `character varying(2)` | YES | | |
255 | | #`state_region` | | `character varying(50)` | YES | | |
256 |
257 | ⇡
258 |
259 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 VB Consulting and Vedran Bilopavlović
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pgcomment
2 |
3 | > WARNING
4 |
5 | > THIS REPOSITORY IS ARCHIVED
6 |
7 | > SOURCE CODE TRANSFERED TO https://github.com/vb-consulting/PgRoutiner
8 |
9 | > All future changes are commited there!
10 |
11 | ## Support
12 |
13 | This is open-source software developed and maintained freely without any compensation whatsoever.
14 |
15 | If you find it useful please consider rewarding me on my effort by [buying me a beer](https://www.paypal.me/vbsoftware/5)🍻 or [buying me a pizza](https://www.paypal.me/vbsoftware/10)🍕
16 |
17 | Or if you prefer bitcoin:
18 | bitcoincash:qp93skpzyxtvw3l3lqqy7egwv8zrszn3wcfygeg0mv
19 |
20 | ## Licence
21 |
22 | Copyright (c) Vedran Bilopavlović - VB Consulting 2020
23 | This source code is licensed under the [MIT license](https://github.com/vbilopav/PgComment/blob/master/LICENSE).
24 |
25 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-slate
--------------------------------------------------------------------------------
/build.bat:
--------------------------------------------------------------------------------
1 | dotnet clean src\PgComment.csproj -c Release
2 | dotnet publish -r win10-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true -o "out\win10-x64" --nologo src\PgComment.csproj
3 | dotnet publish -r linux-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true -o "out\linux-x64" --nologo src\PgComment.csproj
4 | dotnet publish -r osx-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true -o "out\osx-x64" --nologo src\PgComment.csproj
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | dotnet clean src/PgComment.csproj -c Release
2 | dotnet publish -r win10-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true -o "out\win10-x64" --nologo src/PgComment.csproj
3 | dotnet publish -r linux-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true -o "out\linux-x64" --nologo src/PgComment.csproj
4 | dotnet publish -r osx-x64 -c Release /p:PublishSingleFile=true /p:PublishTrimmed=true -o "out\osx-x64" --nologo src/PgComment.csproj
5 |
--------------------------------------------------------------------------------
/src/Markup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Microsoft.Extensions.Configuration;
7 | using Npgsql;
8 |
9 | namespace PgComment
10 | {
11 | internal class Markup
12 | {
13 | public static async Task CreateAllMarkups(IConfiguration config)
14 | {
15 | foreach (var connString in config.GetSection("ConnectionStrings").GetChildren())
16 | {
17 | await CreateMarkupFile(connString.Value);
18 | }
19 | }
20 |
21 | private static async Task CreateMarkupFile(string connectionString)
22 | {
23 | var content = new StringBuilder();
24 | var header = new StringBuilder();
25 |
26 | var settings = Settings.Value;
27 | var schemas = settings.Schemas.Distinct().ToArray();
28 | await using var connection = new NpgsqlConnection(connectionString);
29 | await connection.OpenAsync();
30 | var file = Settings.FileName(connection);
31 |
32 | header.AppendLine($"# Dictionary for database `{connection.Database}`");
33 | header.AppendLine();
34 | header.AppendLine(
35 | $"- Server: PostgreSQL `{connection.Host}:{connection.Port}`, version `{connection.ServerVersion}`");
36 | header.AppendLine($"- Local timestamp: `{DateTime.Now:o}`");
37 | header.AppendLine($"- Schemas: {string.Join(", ", schemas.Select(s => $"`{s}`"))}");
38 | header.AppendLine();
39 | header.AppendLine("## Table of Contents");
40 | header.AppendLine();
41 |
42 | var tablesHeader = false;
43 | var writeToc = false;
44 | var anyTables = false;
45 | var anyViews = false;
46 |
47 | foreach (var schema in schemas)
48 | {
49 | await foreach (var result in connection.GetTables(schema, settings.SkipPattern).ValuesAsync)
50 | {
51 | if (!tablesHeader)
52 | {
53 | //content.AppendLine();
54 | content.AppendLine("## Tables");
55 | tablesHeader = true;
56 | }
57 |
58 | if (result.column == null)
59 | {
60 | Program.Dump("Writing table", result.table);
61 | content.AppendLine();
62 |
63 | if (writeToc)
64 | {
65 | content.AppendLine("⇡");
66 | content.AppendLine();
67 | }
68 | else
69 | {
70 | writeToc = true;
71 | }
72 |
73 | content.AppendLine($"### Table `{schema}.{result.table}`");
74 | header.AppendLine($"- Table [`{schema}.{result.table}`](#table-{schema.ToLower()}{result.table.ToLower()})");
75 | content.AppendLine();
76 | content.AppendLine(Settings.StartTag("table", $"{schema}.\"{result.table}\""));
77 | if (result.comment != null)
78 | {
79 | content.AppendLine(result.comment);
80 | }
81 |
82 | if (!anyTables)
83 | {
84 | anyTables = true;
85 | }
86 | content.AppendLine(Settings.EndTag);
87 | content.AppendLine();
88 | content.AppendLine("| Column | | Type | Nullable | Default | Comment |");
89 | content.AppendLine("| ------ | ----------- | -----| -------- | ------- | ------- |");
90 | }
91 | else
92 | {
93 | var name = $"{schema.ToLower()}-{result.table.ToLower()}-{result.column.ToLower()}";
94 | content.AppendLine(
95 | $"| {Settings.Hashtag(name)}`{result.column}` " +
96 | $"| {result.constraintMarkup} " +
97 | $"| `{result.columnType}` " +
98 | $"| {result.nullable} " +
99 | $"| {result.defaultMarkup} " +
100 | $"| {Settings.StartTag("column", $"{schema}.\"{result.table}\".\"{result.column}\"")}{result.comment}{Settings.EndTag} |");
101 | }
102 | }
103 | }
104 |
105 | if (anyTables)
106 | {
107 | content.AppendLine();
108 | content.AppendLine("⇡");
109 | }
110 |
111 | foreach (var schema in schemas)
112 | {
113 | if (!settings.IncludeViews)
114 | {
115 | break;
116 | }
117 | var viewsHeader = false;
118 | await foreach (var result in connection.GetTables(schema, settings.SkipPattern, "VIEW").ValuesAsync)
119 | {
120 | if (!viewsHeader)
121 | {
122 | content.AppendLine();
123 | content.AppendLine("## Views");
124 | viewsHeader = true;
125 | }
126 |
127 | if (result.column == null)
128 | {
129 | Program.Dump("Writing view", result.table);
130 | content.AppendLine();
131 |
132 | if (writeToc)
133 | {
134 | content.AppendLine("⇡");
135 | content.AppendLine();
136 | }
137 | else
138 | {
139 | writeToc = true;
140 | }
141 | if (!anyViews)
142 | {
143 | anyViews = true;
144 | }
145 |
146 | content.AppendLine($"### View `{schema}.{result.table}`");
147 | header.AppendLine($"- View [`{schema}.{result.table}`](#view-{schema.ToLower()}{result.table.ToLower()})");
148 | content.AppendLine();
149 | content.AppendLine(Settings.StartTag("view", $"{schema}.\"{result.table}\""));
150 | if (result.comment != null)
151 | {
152 | content.AppendLine(result.comment);
153 | }
154 |
155 | content.AppendLine(Settings.EndTag);
156 | content.AppendLine();
157 | content.AppendLine("| Column | Type | Comment |");
158 | content.AppendLine("| ------ | ---- | --------|");
159 | }
160 | else
161 | {
162 | content.AppendLine(
163 | $"| `{result.column}` " +
164 | $"| `{result.columnType}` " +
165 | $"| {Settings.StartTag("column", $"{schema}.\"{result.table}\".\"{result.column}\"")}{result.comment}{Settings.EndTag} |");
166 | }
167 | }
168 | }
169 |
170 | if (anyViews)
171 | {
172 | content.AppendLine();
173 | content.AppendLine("⇡");
174 | }
175 |
176 |
177 | foreach (var schema in schemas)
178 | {
179 | if (!settings.IncludeRoutines)
180 | {
181 | break;
182 | }
183 | var routinesHeader = false;
184 | await foreach (var result in connection.GetRoutines(schema, settings.SkipPattern).ValuesAsync)
185 | {
186 | if (!routinesHeader)
187 | {
188 | content.AppendLine();
189 | content.AppendLine("## Routines");
190 | routinesHeader = true;
191 | }
192 |
193 | Program.Dump("Writing routine", result.name);
194 | content.AppendLine();
195 | content.AppendLine(
196 | $"### {result.type.First().ToString().ToUpper()}{result.type.Substring(1)} `{schema}.{result.signature}`");
197 | var routineAnchor = result.signature.ToLower().Replace("(", "").Replace(")", "").Replace(",", "").Replace(" ", "-");
198 | header.AppendLine($"- {result.type.First().ToString().ToUpper()}{result.type.Substring(1)} [`{schema}.{result.signature}`](#{result.type.ToLower()}-{schema.ToLower()}{routineAnchor})");
199 | content.AppendLine();
200 | content.AppendLine($"- Returns `{result.returns}`");
201 | content.AppendLine();
202 | content.AppendLine($"- Language is `{result.language}`");
203 | content.AppendLine();
204 | content.AppendLine(Settings.StartTag(result.type, $"{schema}.{result.signature.Replace(result.name, $"\"{result.name}\"")}"));
205 | if (result.comment != null)
206 | {
207 | content.AppendLine(result.comment);
208 | }
209 |
210 | content.AppendLine(Settings.EndTag);
211 | if (writeToc)
212 | {
213 | content.AppendLine();
214 | content.AppendLine("⇡");
215 | }
216 | else
217 | {
218 | writeToc = true;
219 | }
220 | }
221 | }
222 |
223 | await connection.CloseAsync();
224 | Program.Dump("Creating file", file);
225 | await File.WriteAllTextAsync(file, content.ToString());
226 | await using (var fileStream = new StreamWriter(file))
227 | {
228 | await fileStream.WriteLineAsync(header.ToString());
229 | await fileStream.WriteLineAsync(content.ToString());
230 | }
231 | Console.WriteLine("Done!");
232 | }
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/src/PgComment.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netcoreapp3.1
6 | pgcomment
7 | 2.1
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | PreserveNewest
23 |
24 |
25 | PreserveNewest
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/PgComment.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29709.97
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PgComment", "PgComment.csproj", "{EEA8380A-8DA1-4EA2-839A-F387076B0298}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {EEA8380A-8DA1-4EA2-839A-F387076B0298}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {EEA8380A-8DA1-4EA2-839A-F387076B0298}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {EEA8380A-8DA1-4EA2-839A-F387076B0298}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {EEA8380A-8DA1-4EA2-839A-F387076B0298}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {566047D4-2EA5-44D5-A60B-E462A8089AC0}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/src/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using Microsoft.Extensions.Configuration;
6 |
7 | namespace PgComment
8 | {
9 | public static class Program
10 | {
11 | public static async Task Main(string[] args)
12 | {
13 | Console.ForegroundColor = ConsoleColor.Yellow;
14 | Console.Write("PgComment ");
15 | Console.ForegroundColor = ConsoleColor.Green;
16 | Console.WriteLine(typeof(Program).Assembly.GetName().Version.ToString());
17 | Console.ResetColor();
18 | Console.WriteLine();
19 |
20 | var currentDir = Directory.GetCurrentDirectory();
21 | var pull = ArgsInclude(args, "-p", "--pull");
22 | var commit = ArgsInclude(args, "-c", "--commit");
23 | var dump = ArgsInclude(args, "-d", "--dump");
24 |
25 | if (ArgsInclude(args, "-h", "--help"))
26 | {
27 | PrintHelp();
28 | return;
29 | }
30 | if (!pull && !commit && !dump)
31 | {
32 | Console.WriteLine();
33 | Console.ForegroundColor = ConsoleColor.Yellow;
34 | Console.WriteLine("Type pgcomment --help for options");
35 | Console.ResetColor();
36 | Console.WriteLine();
37 | return;
38 | }
39 |
40 | string settingsFile = null;
41 | foreach (var arg in args)
42 | {
43 | if (!arg.StartsWith("--settings="))
44 | {
45 | continue;
46 | }
47 | settingsFile = arg.Split("=").Last();
48 | break;
49 | }
50 |
51 | var usingSettingFile = false;
52 | string SettingFile(string name, bool showError = false)
53 | {
54 | var fullName = Path.Join(currentDir, name);
55 | if (File.Exists(fullName))
56 | {
57 | if (!usingSettingFile)
58 | {
59 | Console.ForegroundColor = ConsoleColor.Yellow;
60 | Console.WriteLine("Using setting files:");
61 | usingSettingFile = true;
62 | }
63 | Console.ForegroundColor = ConsoleColor.Green;
64 | Console.WriteLine($" - {name}");
65 | }
66 | else if (showError)
67 | {
68 | Console.ForegroundColor = ConsoleColor.Red;
69 | Console.WriteLine($"File {fullName} could not be found");
70 | }
71 | return fullName;
72 | }
73 |
74 | IConfigurationBuilder configBuilder;
75 | if (settingsFile == null)
76 | {
77 |
78 | configBuilder = new ConfigurationBuilder()
79 | .AddJsonFile(SettingFile("settings.json"), optional: true,
80 | reloadOnChange: false)
81 | .AddJsonFile(SettingFile("appsettings.json"), optional: true,
82 | reloadOnChange: false)
83 | .AddJsonFile(SettingFile("settings.private.json"), optional: true,
84 | reloadOnChange: false)
85 | .AddJsonFile(SettingFile("appsettings.Development.json"), optional: true,
86 | reloadOnChange: false)
87 | .AddCommandLine(args);
88 | }
89 | else
90 | {
91 | configBuilder = new ConfigurationBuilder()
92 | .AddJsonFile(SettingFile(settingsFile, true), optional: true, reloadOnChange: false)
93 | .AddCommandLine(args);
94 | }
95 | Console.ResetColor();
96 |
97 | var config = configBuilder.Build();
98 | Settings.Value = new Settings();
99 | config.GetSection("PgComment").Bind(Settings.Value);
100 | if (Settings.Value.Schemas == null)
101 | {
102 | Settings.Value.Schemas = new[] {"public"};
103 | }
104 |
105 | if (commit || dump)
106 | {
107 | await Update.UpdateFromAllMarkups(config, dump);
108 | }
109 | else
110 | {
111 | await Markup.CreateAllMarkups(config);
112 | }
113 | }
114 |
115 | public static void Dump(string msg, string item)
116 | {
117 | Console.ForegroundColor = ConsoleColor.White;
118 | Console.Write(msg);
119 | Console.Write(" ");
120 | Console.ForegroundColor = ConsoleColor.Yellow;
121 | Console.WriteLine(item);
122 | Console.ResetColor();
123 | }
124 |
125 | private static bool ArgsInclude(string[] args, params string[] values)
126 | {
127 | var lower = values.Select(v => v.ToLower()).ToList();
128 | var upper = values.Select(v => v.ToUpper()).ToList();
129 | foreach (var arg in args)
130 | {
131 | if (lower.Contains(arg))
132 | {
133 | return true;
134 | }
135 |
136 | if (upper.Contains(arg))
137 | {
138 | return true;
139 | }
140 | }
141 | return false;
142 | }
143 |
144 | private static void PrintHelp()
145 | {
146 | Console.WriteLine();
147 | Console.WriteLine("Usage: pgcomment [options] [settings]");
148 | Console.WriteLine();
149 | Console.ForegroundColor = ConsoleColor.Yellow;
150 | Console.WriteLine("options:");
151 | Console.ResetColor();
152 | Console.WriteLine();
153 | PrintItem("-h --help", "This help");
154 | PrintItem("--settings=file", "Path to json settings file");
155 | PrintItem("-p --pull", "Builds markup files based on current settings from comments in database");
156 | PrintItem("-c --commit", "Commits current changes from markup files to database");
157 | PrintItem("-d --dump", "Dumps sql for manual comment update. Doesn't update database");
158 | Console.WriteLine();
159 | Console.ForegroundColor = ConsoleColor.Yellow;
160 | Console.WriteLine("settings:");
161 | Console.ResetColor();
162 | Console.WriteLine();
163 | Console.WriteLine("Settings will override setting.json file settings.");
164 | Console.WriteLine();
165 | PrintItem("pgcomment:markdownname=[name]", "File name to generate or to search. {0} placeholder for database name. Default value is \"DB DICTIONARY {0}.md\"", 40);
166 | PrintItem("pgcomment:schemas[index]=[schema]", "Database schemas to include in generated file.. Multiple schemas separated by zero based index. Default is \"public\"", 40);
167 | PrintItem("pgcomment:skippattern=[pattern]", "Skip object that are similar with this pattern when generating file. Default is \"pg_%\".", 40);
168 | PrintItem("pgcomment:includeviews=[true|false]", "Include views?.", 40);
169 | PrintItem("pgcomment:includeroutines=[true|false]", "Include routines (functions and procedures)?", 40);
170 | PrintItem("connectionstrings:[name]=[connection]", "Connection strings in ADO.NET (Npgsql) format: `Server=;Database=;Port=;User Id=;Password=;`. Each connection have unique name.", 40);
171 | Console.WriteLine();
172 | }
173 |
174 | private static void PrintItem(string options, string description, int left = 20)
175 | {
176 | Console.ForegroundColor = ConsoleColor.Green;
177 | Console.Write($" {options}");
178 | if (left == -1)
179 | {
180 | Console.WriteLine();
181 | Console.CursorLeft = 20;
182 | }
183 | else
184 | {
185 | Console.CursorLeft = left;
186 | }
187 | Console.ResetColor();
188 | Console.WriteLine(description);
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/Settings.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Npgsql;
3 |
4 | namespace PgComment
5 | {
6 | public class Settings
7 | {
8 | public string MarkdownName { get; set; } = "DB DICTIONARY {0}.md";
9 | public string[] Schemas { get; set; } = null;
10 | public string SkipPattern { get; set; } = "pg_%";
11 | public bool IncludeRoutines { get; set; } = true;
12 | public bool IncludeViews { get; set; } = true;
13 |
14 | public static Settings Value { get; set; }
15 |
16 | public static string Open => "";
18 | public static string CommentStatement => "comment on ";
19 | public static string Param => "@until-end-tag";
20 | public static string CommentIs => $" is {Param};";
21 | public static string StartTag(string on, string name) => $"{Open}{CommentStatement}{on} {name}{CommentIs}{Close}";
22 | public static string EndTag => $"{Open}end{Close}";
23 |
24 | public static string FileName(NpgsqlConnection connection) =>
25 | Path.Combine(Directory.GetCurrentDirectory(), string.Format(Value.MarkdownName, connection.Database));
26 |
27 | public static string Hashtag(string name) =>
28 | $"#";
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Sql.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Norm.Extensions;
5 | using Npgsql;
6 |
7 | namespace PgComment
8 | {
9 | public class TableResults
10 | {
11 | public IAsyncEnumerable<(
12 | string table,
13 | string column,
14 | string constraintMarkup,
15 | string columnType,
16 | string nullable,
17 | string defaultMarkup,
18 | string comment)>
19 | ValuesAsync { get; set; }
20 | }
21 |
22 | public class RoutineResults
23 | {
24 | public IAsyncEnumerable<(
25 | string type,
26 | string name,
27 | string signature,
28 | string returns,
29 | string language,
30 | string comment)>
31 | ValuesAsync { get; set; }
32 | }
33 |
34 | public static class Sql
35 | {
36 | public static TableResults GetTables(this NpgsqlConnection connection, string schema, string skipPattern, string type = "BASE TABLE") => new TableResults
37 | {
38 | ValuesAsync = connection.ReadAsync(@"
39 |
40 | with table_constraints as (
41 |
42 | select
43 | sub.table_name,
44 | sub.column_name,
45 | string_agg(
46 | sub.description_markup,
47 | ', '
48 | order by
49 | case
50 | when sub.description_markup = '**PK**' then ' '
51 | else sub.description_markup
52 | end
53 | ) as description_markup
54 | from (
55 | select
56 | tc.table_name,
57 | coalesce(kcu.column_name, ccu.column_name) as column_name,
58 | case when tc.constraint_type = 'PRIMARY KEY'
59 | then '**PK**'
60 | when tc.constraint_type = 'FOREIGN KEY'
61 | then '**FK [➝](#' || lower(ccu.table_schema || '-' || ccu.table_name || '-' || ccu.column_name) || ') `' ||
62 | case when tc.constraint_schema = ccu.table_schema
63 | then ''
64 | else ccu.table_schema || '.'
65 | end
66 | || ccu.table_name || '.' || ccu.column_name || '`**'
67 | else tc.constraint_type
68 | end as description_markup
69 |
70 | from
71 | information_schema.table_constraints tc
72 | inner join information_schema.constraint_column_usage ccu
73 | on tc.constraint_schema = ccu.constraint_schema and tc.constraint_name = ccu.constraint_name
74 | left outer join information_schema.key_column_usage kcu
75 | on tc.constraint_type = 'FOREIGN KEY' and tc.constraint_schema = kcu.constraint_schema and ccu.constraint_name = kcu.constraint_name
76 | where
77 | tc.constraint_schema = @schema
78 |
79 | union all
80 |
81 | select
82 | i.relname as table_name,
83 | a.attname as column_name,
84 | '**IDX**' as description_markup
85 | from
86 | pg_stat_all_indexes i
87 | inner join pg_attribute a on i.indexrelid = a.attrelid
88 | left outer join information_schema.table_constraints tc on i.indexrelname = tc.constraint_name
89 | where tc.table_name is null and i.schemaname = @schema
90 |
91 | order by
92 | description_markup
93 |
94 | ) sub
95 | group by
96 | sub.table_name, sub.column_name
97 |
98 | )
99 | select
100 | table_name_id as table_name,
101 | c.column_name,
102 | tc.description_markup,
103 | c.data_type ||
104 |
105 | case when c.data_type <> 'integer' and c.data_type <> 'bigint' and c.data_type <> 'smallint'
106 | then
107 | case when c.character_maximum_length is not null
108 | then '(' || cast(c.character_maximum_length as varchar) || ')'
109 | else
110 | case when c.numeric_precision is not null
111 | then '(' || cast(c.numeric_precision as varchar) || ',' || cast(c.numeric_precision_radix as varchar) || ')' ||
112 | case when coalesce(c.numeric_scale, 0) = 0
113 | then ''
114 | else ',' || cast(c.numeric_scale as varchar) || ')'
115 | end
116 | else ''
117 | end
118 | end
119 | else ''
120 | end as data_type,
121 |
122 | case when c.is_nullable = 'NO' then '**NO**' else c.is_nullable end as nullableMarkup,
123 |
124 | case when c.column_default like 'next%' or c.identity_generation = 'ALWAYS'
125 | then '*auto increment*'
126 | else '`' || c.column_default || '`'
127 | end as defaultMarkup,
128 |
129 | pgdesc.description
130 |
131 | from (
132 | select t1.table_name as table_name_id, t1.table_name
133 | from information_schema.tables t1
134 | where t1.table_schema = @schema and t1.table_type = @type
135 | union all
136 | select t2.table_name as table_name_id, null as table_name
137 | from information_schema.tables t2
138 | where t2.table_schema = @schema and t2.table_type = @type
139 | order by table_name_id, table_name nulls first
140 | ) t
141 |
142 | left outer join information_schema.columns c
143 | on t.table_name = c.table_name and c.table_schema = @schema
144 |
145 | left outer join pg_catalog.pg_statio_user_tables pgtbl
146 | on t.table_name_id = pgtbl.relname and pgtbl.schemaname = @schema
147 |
148 | left outer join pg_catalog.pg_description pgdesc
149 | on pgtbl.relid = pgdesc.objoid and coalesce(c.ordinal_position, 0) = pgdesc.objsubid
150 |
151 | left outer join table_constraints tc
152 | on t.table_name = tc.table_name and c.column_name = tc.column_name
153 |
154 | where table_name_id not similar to @skipPattern
155 |
156 | order by
157 | t.table_name_id,
158 | t.table_name nulls first,
159 | c.ordinal_position", ("schema", schema), ("skipPattern", skipPattern), ("type", type)
160 | )
161 | };
162 |
163 | public static RoutineResults GetRoutines(this NpgsqlConnection connection, string schema, string skipPattern) => new RoutineResults
164 | {
165 | ValuesAsync = connection.ReadAsync(@"
166 |
167 | select
168 | lower(r.routine_type) as type,
169 | r.routine_name,
170 |
171 | r.routine_name ||
172 | '(' ||
173 | array_to_string(
174 | array_agg(
175 | case when p.parameter_mode = 'IN'
176 | then '' else lower(p.parameter_mode) || ' '
177 | end || coalesce(case when p.data_type = 'ARRAY' then regexp_replace(p.udt_name, '^[_]', '') || '[]' else p.data_type end, '')
178 | order by p.ordinal_position
179 | ),
180 | ', '
181 | ) ||
182 | ')' as signature,
183 |
184 | case when r.data_type = 'USER-DEFINED' and
185 | r.type_udt_catalog is not null and
186 | r.type_udt_schema is not null and
187 | r.type_udt_name is not null
188 | then 'setof ' || r.type_udt_name
189 | else r.data_type
190 | end as returns_type,
191 |
192 | lower(r.external_language) as language,
193 |
194 | pgdesc.description
195 |
196 | from
197 | information_schema.routines r
198 | left outer join information_schema.parameters p
199 | on r.specific_name = p.specific_name and r.specific_schema = p.specific_schema
200 |
201 | inner join pg_catalog.pg_proc proc on r.routine_name = proc.proname
202 | left outer join pg_catalog.pg_description pgdesc on proc.oid = pgdesc.objoid
203 | where
204 | r.specific_schema = @schema
205 | and r.external_language <> 'INTERNAL'
206 | and r.routine_name not similar to @skipPattern
207 |
208 | group by
209 | r.specific_name, r.routine_type, r.external_language, r.routine_name,
210 | r.data_type, r.type_udt_catalog, r.type_udt_schema, r.type_udt_name,
211 | pgdesc.description
212 |
213 | ", ("schema", schema), ("skipPattern", skipPattern))
214 | };
215 | }
216 | }
--------------------------------------------------------------------------------
/src/Update.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Threading.Tasks;
6 | using Microsoft.Extensions.Configuration;
7 | using Norm.Extensions;
8 | using Npgsql;
9 |
10 | namespace PgComment
11 | {
12 | internal class Update
13 | {
14 | public static async Task UpdateFromAllMarkups(IConfiguration config, bool dump)
15 | {
16 | foreach (var connString in config.GetSection("ConnectionStrings").GetChildren())
17 | {
18 | await UpdateFromMarkupFile(connString.Value, dump);
19 | }
20 | }
21 |
22 | private static async Task UpdateFromMarkupFile(string connectionString, bool dump)
23 | {
24 | var settings = Settings.Value;
25 | await using var connection = new NpgsqlConnection(connectionString);
26 | await connection.OpenAsync();
27 | var file = Settings.FileName(connection);
28 | Program.Dump("Reading file", file);
29 | Console.WriteLine();
30 | if (!File.Exists(file))
31 | {
32 | Console.ForegroundColor = ConsoleColor.Red;
33 | Console.WriteLine($"File {file} does not exists, skipping ...");
34 | Console.ResetColor();
35 | return;
36 | }
37 | var content = await File.ReadAllTextAsync(file);
38 |
39 | var comments = new Dictionary();
40 |
41 | foreach (var schema in settings.Schemas.Distinct())
42 | {
43 | var dict = await connection
44 | .GetTables(schema, settings.SkipPattern)
45 | .ValuesAsync
46 | .Where(t => t.comment != null)
47 | .ToDictionaryAsync(
48 | t => $"{schema}.{t.table}{(t.column == null ? "" : $".{t.column}")}",
49 | t => t.comment);
50 |
51 | dict.ToList().ForEach(x => comments.Add(x.Key, x.Value));
52 | }
53 |
54 |
55 | Console.WriteLine("do $comments_update$");
56 | Console.WriteLine("begin");
57 | Console.WriteLine("");
58 |
59 | if (!dump)
60 | {
61 | await connection.ExecuteAsync("begin");
62 | }
63 |
64 | var start = 0;
65 | var search = $"{Settings.Open}{Settings.CommentStatement}";
66 | var close = Settings.Close;
67 | var endTag = Settings.EndTag;
68 | while (true)
69 | {
70 | start = content.IndexOf(search, start, StringComparison.Ordinal);
71 | if (start == -1)
72 | {
73 | break;
74 | }
75 | var end = content.IndexOf(close, start, StringComparison.Ordinal);
76 | if (end == -1)
77 | {
78 | break;
79 | }
80 | var commentTag = content.Substring(start, end - start + close.Length);
81 | start = end + close.Length;
82 | end = content.IndexOf(endTag, start, StringComparison.Ordinal);
83 | if (end == -1)
84 | {
85 | break;
86 | }
87 | var comment = content.Substring(start, end - start).Trim();
88 | start = end;
89 |
90 | var statement = commentTag
91 | .Replace(Settings.Open, "")
92 | .Replace(Settings.Close, "");
93 | var part = statement
94 | .Replace(Settings.CommentStatement, "")
95 | .Replace(Settings.CommentIs, "");
96 | var sep = part.IndexOf(" ", StringComparison.Ordinal) + 1;
97 | var entry = part.Substring(sep, part.Length - sep);
98 |
99 | try
100 | {
101 | if (comments.TryGetValue(entry, out var old))
102 | {
103 | if (!string.Equals(comment, old.Trim()))
104 | {
105 | Execute(connection, statement, comment, dump);
106 | }
107 | }
108 | else
109 | {
110 | if (!string.IsNullOrEmpty(comment))
111 | {
112 | Execute(connection, statement, comment, dump);
113 | }
114 | }
115 | }
116 | catch (Exception)
117 | {
118 | connection.Execute("rollback");
119 | throw;
120 | }
121 | }
122 |
123 |
124 | Console.WriteLine();
125 | Console.WriteLine("end");
126 | Console.WriteLine("$comments_update$;");
127 |
128 | if (!dump)
129 | {
130 | await connection.ExecuteAsync("end");
131 | Console.WriteLine();
132 | Console.WriteLine("Database updated successfully!");
133 | }
134 |
135 | }
136 |
137 | private static void Execute(NpgsqlConnection connection, string statement, string comment, bool dump)
138 | {
139 | var command = statement.Replace(Settings.Param, $"$${comment}$$");
140 | Console.WriteLine($" {command}");
141 | if (!dump)
142 | {
143 | connection.Execute(command);
144 | }
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | /*
3 | "PgComment": {
4 | "MarkdownName": "DB DICTIONARY {0}.md",
5 | "Schemas": [ "public" ],
6 | "SkipPattern": "pg_%",
7 | "IncludeViews": true,
8 | "IncludeRoutines": true
9 | },
10 | "ConnectionStrings": {
11 | "Connection1": "Server=localhost;Database=;Port=5432;User Id=;Password=;"
12 | }
13 | */
14 | }
--------------------------------------------------------------------------------