├── .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 | } --------------------------------------------------------------------------------