From d6d41f57103207dda122c70b7dde61f5633f47b9 Mon Sep 17 00:00:00 2001 From: mah-kh58 Date: Sat, 22 Nov 2025 18:03:37 +0100 Subject: [PATCH] Update the project to 'Microsoft.NET.Sdk' and use VS Code. Add row called 'Fields (all tables)' to the table 'schema'. Add the table 'Tables Records' after the table schema to show all tables with a summary of the number of records and clickable table names. --- .gitattributes | 63 + .github/copilot-instructions.md | 68 + .gitignore | 156 + .vscode/launch.json | 14 + .vscode/settings.json | 3 + .vscode/tasks.json | 17 + ...rDatabaseDocumentationGenerator-master.zip | Bin 0 -> 189593 bytes AdventureWorks-example.html | 4565 +++++++++++++++++ DbDocGenerator/App.config | 6 + DbDocGenerator/DbDocGenerator.csproj | 16 + DbDocGenerator/Program.cs | 15 + DbDocGenerator/Properties/AssemblyInfo.cs | 36 + DocumentationGeneratorApplication/App.config | 12 + .../DocumentationGeneratorApplication.csproj | 21 + .../FrmConnectionString.Designer.cs | 251 + .../FrmConnectionString.cs | 308 ++ .../FrmConnectionString.resx | 123 + .../FrmObjectsWithoutDescription.Designer.cs | 149 + .../FrmObjectsWithoutDescription.cs | 85 + .../FrmObjectsWithoutDescription.resx | 129 + .../MainForm.Designer.cs | 236 + DocumentationGeneratorApplication/MainForm.cs | 330 ++ .../MainForm.resx | 129 + DocumentationGeneratorApplication/Program.cs | 22 + .../Properties/AssemblyInfo.cs | 36 + .../Properties/Resources.Designer.cs | 63 + .../Properties/Resources.resx | 117 + .../Properties/Settings.Designer.cs | 26 + .../Properties/Settings.settings | 7 + .../Collections/Generic/PropertyComparer.cs | 52 + .../ComponentModel/FilterList.cs | 66 + .../PropertyDescriptorHelper.cs | 39 + .../ComponentModel/SortableBindingList.cs | 102 + DocumentationGeneratorConsole/App.config | 6 + .../DocumentationGeneratorConsole.csproj | 20 + DocumentationGeneratorConsole/Program.cs | 34 + .../Properties/AssemblyInfo.cs | 36 + LICENSE.txt | 41 + Readme.md | 11 + SqlServerDatabaseDocumentationGenerator.sln | 39 + .../Document/DatabaseHtmlDocumentGenerator.cs | 1628 ++++++ .../DocumentGeneratorConfiguration.cs | 17 + .../Document/FormattingExtensionMethod.cs | 114 + .../Inspection/ColumnInspector.cs | 147 + .../Inspection/CommonInspector.cs | 23 + .../Inspection/DatabaseInspector.cs | 88 + .../DatabaseDesignIssueInspector.cs | 884 ++++ .../StoredProcedureDesignIssueInspector.cs | 73 + .../Inspection/ForeignKeyInspector.cs | 92 + .../Inspection/IndexInspector.cs | 83 + .../Inspection/ParameterInspector.cs | 141 + .../Inspection/ScalarFunctionInspector.cs | 72 + .../Inspection/SchemaInspector.cs | 75 + .../Inspection/StoredProcedureInspector.cs | 59 + .../Inspection/TableFunctionInspector.cs | 73 + .../Inspection/TableInspector.cs | 72 + .../Inspection/ViewInspector.cs | 70 + .../Model/Column.cs | 56 + .../Model/Database.cs | 133 + .../Model/DesignIssueWarning.cs | 19 + .../Model/ForeignKey.cs | 44 + .../Model/ForeignKeyColumn.cs | 25 + .../Model/IDbObject.cs | 30 + .../Model/IDbRoutine.cs | 17 + .../Model/IUserDefinedFunction.cs | 23 + .../Model/Index.cs | 34 + .../Model/Parameter.cs | 36 + .../Model/ScalarFunction.cs | 37 + .../Model/Schema.cs | 36 + .../Model/StoredProcedure.cs | 29 + .../Model/Table.cs | 54 + .../Model/TableFunction.cs | 30 + .../Model/View.cs | 60 + .../Properties/AssemblyInfo.cs | 36 + ...erverDatabaseDocumentationGenerator.csproj | 20 + .../ToDoList.txt | 36 + .../Utility/ConnectionTestResult.cs | 15 + .../Utility/ExtendedPropertyExtension.cs | 161 + .../Utility/IDbObjectExtension.cs | 109 + .../Utility/IEnumerableExtension.cs | 22 + .../Utility/SqlConnectionTester.cs | 49 + .../Utility/SqlExtension.cs | 71 + .../third-party/PetaPoco.cs | 2376 +++++++++ .../third-party/bootstrap/bootstrap.min.css | 7 + .../Properties/AssemblyInfo.cs | 36 + ...rDatabaseDocumentationGeneratorTest.csproj | 23 + .../TestExtendedPropertyExtension.cs | 66 + 87 files changed, 14850 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/copilot-instructions.md create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 001_Source/SqlServerDatabaseDocumentationGenerator-master.zip create mode 100644 AdventureWorks-example.html create mode 100644 DbDocGenerator/App.config create mode 100644 DbDocGenerator/DbDocGenerator.csproj create mode 100644 DbDocGenerator/Program.cs create mode 100644 DbDocGenerator/Properties/AssemblyInfo.cs create mode 100644 DocumentationGeneratorApplication/App.config create mode 100644 DocumentationGeneratorApplication/DocumentationGeneratorApplication.csproj create mode 100644 DocumentationGeneratorApplication/FrmConnectionString.Designer.cs create mode 100644 DocumentationGeneratorApplication/FrmConnectionString.cs create mode 100644 DocumentationGeneratorApplication/FrmConnectionString.resx create mode 100644 DocumentationGeneratorApplication/FrmObjectsWithoutDescription.Designer.cs create mode 100644 DocumentationGeneratorApplication/FrmObjectsWithoutDescription.cs create mode 100644 DocumentationGeneratorApplication/FrmObjectsWithoutDescription.resx create mode 100644 DocumentationGeneratorApplication/MainForm.Designer.cs create mode 100644 DocumentationGeneratorApplication/MainForm.cs create mode 100644 DocumentationGeneratorApplication/MainForm.resx create mode 100644 DocumentationGeneratorApplication/Program.cs create mode 100644 DocumentationGeneratorApplication/Properties/AssemblyInfo.cs create mode 100644 DocumentationGeneratorApplication/Properties/Resources.Designer.cs create mode 100644 DocumentationGeneratorApplication/Properties/Resources.resx create mode 100644 DocumentationGeneratorApplication/Properties/Settings.Designer.cs create mode 100644 DocumentationGeneratorApplication/Properties/Settings.settings create mode 100644 DocumentationGeneratorApplication/third-party/BeTimvwFramework/Collections/Generic/PropertyComparer.cs create mode 100644 DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/FilterList.cs create mode 100644 DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/PropertyDescriptorHelper.cs create mode 100644 DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/SortableBindingList.cs create mode 100644 DocumentationGeneratorConsole/App.config create mode 100644 DocumentationGeneratorConsole/DocumentationGeneratorConsole.csproj create mode 100644 DocumentationGeneratorConsole/Program.cs create mode 100644 DocumentationGeneratorConsole/Properties/AssemblyInfo.cs create mode 100644 LICENSE.txt create mode 100644 Readme.md create mode 100644 SqlServerDatabaseDocumentationGenerator.sln create mode 100644 SqlServerDatabaseDocumentationGenerator/Document/DatabaseHtmlDocumentGenerator.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Document/DocumentGeneratorConfiguration.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Document/FormattingExtensionMethod.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/ColumnInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/CommonInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/DatabaseInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/DatabaseDesignIssueInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/StoredProcedureDesignIssueInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/ForeignKeyInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/IndexInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/ParameterInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/ScalarFunctionInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/SchemaInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/StoredProcedureInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/TableFunctionInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/TableInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Inspection/ViewInspector.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/Column.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/Database.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/DesignIssueWarning.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/ForeignKey.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/ForeignKeyColumn.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/IDbObject.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/IDbRoutine.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/IUserDefinedFunction.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/Index.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/Parameter.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/ScalarFunction.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/Schema.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/StoredProcedure.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/Table.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/TableFunction.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Model/View.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Properties/AssemblyInfo.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/SqlServerDatabaseDocumentationGenerator.csproj create mode 100644 SqlServerDatabaseDocumentationGenerator/ToDoList.txt create mode 100644 SqlServerDatabaseDocumentationGenerator/Utility/ConnectionTestResult.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Utility/ExtendedPropertyExtension.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Utility/IDbObjectExtension.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Utility/IEnumerableExtension.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Utility/SqlConnectionTester.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/Utility/SqlExtension.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/third-party/PetaPoco.cs create mode 100644 SqlServerDatabaseDocumentationGenerator/third-party/bootstrap/bootstrap.min.css create mode 100644 SqlServerDatabaseDocumentationGeneratorTest/Properties/AssemblyInfo.cs create mode 100644 SqlServerDatabaseDocumentationGeneratorTest/SqlServerDatabaseDocumentationGeneratorTest.csproj create mode 100644 SqlServerDatabaseDocumentationGeneratorTest/TestExtendedPropertyExtension.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..960ed18 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,68 @@ +## Purpose + +This repository generates human-readable HTML documentation for Microsoft SQL Server databases (2005+). +This file gives pragmatic, repo-specific guidance for an AI coding assistant to be productive quickly. + +## Quick Tasks + +- **Build:** run `dotnet build` at the repository root (solution file `SqlServerDatabaseDocumentationGenerator.sln`). VS2019/VS2022 can also be used for .NET Framework builds. +- **Run Console Example:** edit `DocumentationGeneratorConsole/Program.cs` to change the connection string, then run the Console project; it writes `database.html` to the current directory. +- **Run GUI:** `DocumentationGeneratorApplication` is a WinForms app (MainForm.cs). Launch from Visual Studio to use the interactive connection-string dialog. + +## Key Components (big picture) + +- **Entry projects:** + - `DocumentationGeneratorApplication/` — WinForms GUI, interactive connection string and save dialog (MainForm.cs, FrmConnectionString.cs). + - `DocumentationGeneratorConsole/` — small console runner (Program.cs), currently uses a hard-coded `connStr` and writes `database.html`. + - `SqlServerDatabaseDocumentationGenerator/` — core library: Document, Inspection, Model, Utility, and DesignIssue folders. + +- **Core flow:** + 1. `DatabaseInspector` (in `Inspection/`) reads metadata from SQL Server (schemas, tables, views, columns, routines) into Model objects. + 2. `DatabaseHtmlDocumentGenerator` (in `Document/`) converts `Model.Database` to an HTML document using an internal `HtmlTextWriter` fallback. + 3. Output is an HTML file (Bootstrap CSS included inline). + +## Repo-specific patterns & conventions + +- Database object descriptions are pulled from the extended property named `MS_Description` (expect code to refer to this). +- The `Document/DatabaseHtmlDocumentGenerator.cs` contains a self-contained fallback for `System.Web.UI.HtmlTextWriter` — careful when adding System.Web references or testing on environments without it. +- Inspector classes follow the pattern `*Inspector.cs` (e.g., `TableInspector.cs`, `SchemaInspector.cs`) and populate POCOs in `Model/` — prefer working through the inspector methods to change how metadata is collected. + +## Important files to inspect/edit + +- `SqlServerDatabaseDocumentationGenerator/Document/DatabaseHtmlDocumentGenerator.cs` — main HTML generation; contains the app version and base CSS. +- `SqlServerDatabaseDocumentationGenerator/Inspection/DatabaseInspector.cs` — orchestrates other inspectors and exposes `GetDatabaseMetaData()`. +- `DocumentationGeneratorConsole/Program.cs` — minimal runnable example; useful for automation and CI. +- `DocumentationGeneratorApplication/` — UI-specific code (WinForms): `MainForm.cs`, `FrmConnectionString.cs`. +- `SqlServerDatabaseDocumentationGenerator.sln` and `.vscode/tasks.json` (task `build`) — canonical build targets. + +## Building, testing, and debugging notes + +- The solution targets .NET Framework 4.8 (Readme states 4.8, compatible with 4.5+). Use Visual Studio for WinForms debugging. +- To build from terminal (cross-machine): + +``` +dotnet build "SqlServerDatabaseDocumentationGenerator.sln" +``` + +- The console project currently hardcodes a connection string — update `Program.cs` or add CLI arg parsing before running in non-dev environments. +- Output HTML (example) is at `AdventureWorks-example.html` in the repo root — use it as a sample for layout and expected content. + +## Integration & external dependencies + +- Expects a reachable Microsoft SQL Server instance and valid connection string (Integrated or SQL auth). +- Third-party code lives under `third-party/` (e.g., `BeTimvwFramework`); inspect those for license/compat issues. + +## Coding guidance for PRs + +- Preserve public model shapes in `Model/` where possible — many components (inspectors, document generator) rely on these POCOs. +- When modifying HTML output, edit `DatabaseHtmlDocumentGenerator.cs`. There is an inline `baseCss` and bootstrap usage; search for `baseCss` in that file. +- Avoid introducing a hard dependency on `System.Web` — the repo intentionally includes a lightweight fallback. If you add System.Web references, update project files and ensure builds still succeed on CI/targets. + +## Helpful searches/examples + +- To find metadata collection code: search for `GetDatabaseMetaData`, `DatabaseInspector`, or names under `Inspection/`. +- To find where descriptions are read: search for `MS_Description` or `ExtendedProperty` string patterns. + +## If you need more + +If any part of the flow or environment is unclear (e.g., which project is used in a specific CI or how tests are run), tell me which area to expand and I will update this file with concrete commands and examples. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bc915c --- /dev/null +++ b/.gitignore @@ -0,0 +1,156 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ce55aa6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch DocumentationGeneratorConsole (exe)", + "type": "coreclr", + "request": "launch", + "program": "${workspaceFolder}/DocumentationGeneratorConsole/bin/Debug/net40/DocumentationGeneratorConsole.exe", + "args": [], + "cwd": "${workspaceFolder}/DocumentationGeneratorConsole", + "console": "internalConsole" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..013007b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dotnet.preferCSharpExtension": true +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..369ad88 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}", + "/property:GenerateFullPaths=true", + "/consoleLoggerParameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/001_Source/SqlServerDatabaseDocumentationGenerator-master.zip b/001_Source/SqlServerDatabaseDocumentationGenerator-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..12fa4c139c82562d46558042bd422fe46b0ccce9 GIT binary patch literal 189593 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�m1IUHG;KE@jyU)BbAiJRe9G)Feei_%KeFl(b4 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 literal 0 HcmV?d00001 diff --git a/AdventureWorks-example.html b/AdventureWorks-example.html new file mode 100644 index 0000000..34f6bc5 --- /dev/null +++ b/AdventureWorks-example.html @@ -0,0 +1,4565 @@ + + + + + AdventureWorks + + +

+ AdventureWorks Database +

+ dbo (schema) +

Primary filegroup for the AdventureWorks sample database.

+ + + + + + + + + + + + + + + + + +
Object TypeCount
Tables3
Views0
Stored procedures6
Scalar functions10
Table functions1

+ dbo.AWBuildVersion (table) +

+ + + + + + + + + + + + + + + +
+ Current version number of the AdventureWorks sample database. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SystemInformationIDtinyint (identity)3,0NononeNoPrimary key for AWBuildVersion records.
Database Versionnvarchar25NononeNoVersion number of the database in 9.yy.mm.dd.00 format.
VersionDatedatetimeNononeNoDate and time the record was last updated.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on AWBuildVersion +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_AWBuildVersion_SystemInformationIDClustered index created by a primary key constraint.YesYesCLUSTEREDSystemInformationID

+ dbo.DatabaseLog (table) +

+ + + + + + + + + + + + + + + + + + + + + + + +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
DatabaseLogIDint (identity)10,0NononeNoPrimary key for DatabaseLog records.
PostTimedatetimeNononeNoThe date and time the DDL change occurred.
DatabaseUsernvarchar128NononeNoThe user who implemented the DDL change.
Eventnvarchar128NononeNoThe type of DDL statement that was executed.
Schemanvarchar128YesnoneNoThe schema to which the changed object belongs.
Objectnvarchar128YesnoneNoThe object that was changed by the DDL statment.
TSQLnvarcharNononeNoThe exact Transact-SQL statement that was executed.
XmlEventxmlNononeNoThe raw XML data generated by database trigger.
+ + + + + + + + + +
+ Indexes on DatabaseLog +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_DatabaseLog_DatabaseLogIDNonclustered index created by a primary key constraint.YesYesNONCLUSTEREDDatabaseLogID

+ dbo.ErrorLog (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Audit table tracking errors in the the AdventureWorks database that are caught by the CATCH block of a TRY...CATCH construct. Data is inserted by stored procedure dbo.uspLogError when it is executed from inside the CATCH block of a TRY...CATCH construct. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ErrorLogIDint (identity)10,0NononeNoPrimary key for ErrorLog records.
ErrorTimedatetimeNo(getdate())NoThe date and time at which the error occurred.
UserNamenvarchar128NononeNoThe user who executed the batch in which the error occurred.
ErrorNumberint10,0NononeNoThe error number of the error that occurred.
ErrorSeverityint10,0YesnoneNoThe severity of the error that occurred.
ErrorStateint10,0YesnoneNoThe state number of the error that occurred.
ErrorProcedurenvarchar126YesnoneNoThe name of the stored procedure or trigger where the error occurred.
ErrorLineint10,0YesnoneNoThe line number at which the error occurred.
ErrorMessagenvarchar4000NononeNoThe message text of the error that occurred.
+ + + + + + + + + +
+ Indexes on ErrorLog +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ErrorLog_ErrorLogIDClustered index created by a primary key constraint.YesYesCLUSTEREDErrorLogID

+ dbo.uspGetBillOfMaterials (stored procedure) +

Stored procedure using a recursive query to return a multi-level bill of material for the specified ProductID.

+ + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@StartProductIDint10,0InputInput parameter for the stored procedure uspGetBillOfMaterials. Enter a valid ProductID from the Production.Product table.
@CheckDatedatetimeInputInput parameter for the stored procedure uspGetBillOfMaterials used to eliminate components not used after that date. Enter a valid date.

+ dbo.uspGetEmployeeManagers (stored procedure) +

Stored procedure using a recursive query to return the direct and indirect managers of the specified employee.

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@EmployeeIDint10,0InputInput parameter for the stored procedure uspGetEmployeeManagers. Enter a valid EmployeeID from the HumanResources.Employee table.

+ dbo.uspGetManagerEmployees (stored procedure) +

Stored procedure using a recursive query to return the direct and indirect employees of the specified manager.

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@ManagerIDint10,0InputInput parameter for the stored procedure uspGetManagerEmployees. Enter a valid ManagerID from the HumanResources.Employee table.

+ dbo.uspGetWhereUsedProductID (stored procedure) +

Stored procedure using a recursive query to return all components or assemblies that directly or indirectly use the specified ProductID.

+ + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@StartProductIDint10,0InputInput parameter for the stored procedure uspGetWhereUsedProductID. Enter a valid ProductID from the Production.Product table.
@CheckDatedatetimeInputInput parameter for the stored procedure uspGetWhereUsedProductID used to eliminate components not used after that date. Enter a valid date.

+ dbo.uspLogError (stored procedure) +

Logs error information in the ErrorLog table about the error that caused execution to jump to the CATCH block of a TRY...CATCH construct. Should be executed from within the scope of a CATCH block otherwise it will return without inserting error information.

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@ErrorLogIDint10,0OutputOutput parameter for the stored procedure uspLogError. Contains the ErrorLogID value corresponding to the row inserted by uspLogError in the ErrorLog table.

+ dbo.uspPrintError (stored procedure) +

Prints error information about the error that caused execution to jump to the CATCH block of a TRY...CATCH construct. Should be executed from within the scope of a CATCH block otherwise it will return without printing any error information.

+ dbo.ufnGetAccountingEndDate (scalar function) +

Scalar function used in the uSalesOrderHeader trigger to set the starting account date.

Returns datetime

+ dbo.ufnGetAccountingStartDate (scalar function) +

Scalar function used in the uSalesOrderHeader trigger to set the ending account date.

Returns datetime

+ dbo.ufnGetDocumentStatusText (scalar function) +

Scalar function returning the text representation of the Status column in the Document table.

Returns nvarchar(16)

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@Statustinyint3,0Input parameter for the scalar function ufnGetDocumentStatusText. Enter a valid integer.

+ dbo.ufnGetProductDealerPrice (scalar function) +

Scalar function returning the dealer price for a given product on a particular order date.

Returns money(19,4)

+ + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@ProductIDint10,0Input parameter for the scalar function ufnGetProductDealerPrice. Enter a valid ProductID from the Production.Product table.
@OrderDatedatetimeInput parameter for the scalar function ufnGetProductDealerPrice. Enter a valid order date.

+ dbo.ufnGetProductListPrice (scalar function) +

Scalar function returning the list price for a given product on a particular order date.

Returns money(19,4)

+ + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@ProductIDint10,0Input parameter for the scalar function ufnGetProductListPrice. Enter a valid ProductID from the Production.Product table.
@OrderDatedatetimeInput parameter for the scalar function ufnGetProductListPrice. Enter a valid order date.

+ dbo.ufnGetProductStandardCost (scalar function) +

Scalar function returning the standard cost for a given product on a particular order date.

Returns money(19,4)

+ + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@ProductIDint10,0Input parameter for the scalar function ufnGetProductStandardCost. Enter a valid ProductID from the Production.Product table.
@OrderDatedatetimeInput parameter for the scalar function ufnGetProductStandardCost. Enter a valid order date.

+ dbo.ufnGetPurchaseOrderStatusText (scalar function) +

Scalar function returning the text representation of the Status column in the PurchaseOrderHeader table.

Returns nvarchar(15)

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@Statustinyint3,0Input parameter for the scalar function ufnGetPurchaseOrdertStatusText. Enter a valid integer.

+ dbo.ufnGetSalesOrderStatusText (scalar function) +

Scalar function returning the text representation of the Status column in the SalesOrderHeader table.

Returns nvarchar(15)

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@Statustinyint3,0Input parameter for the scalar function ufnGetSalesOrderStatusText. Enter a valid integer.

+ dbo.ufnGetStock (scalar function) +

Scalar function returning the quantity of inventory in LocationID 6 (Miscellaneous Storage)for a specified ProductID.

Returns int(10,0)

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@ProductIDint10,0Input parameter for the scalar function ufnGetStock. Enter a valid ProductID from the Production.ProductInventory table.

+ dbo.ufnLeadingZeros (scalar function) +

Scalar function used by the Sales.Customer table to help set the account number.

Returns varchar(8)

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@Valueint10,0Input parameter for the scalar function ufnLeadingZeros. Enter a valid integer.

+ dbo.ufnGetContactInformation (table function) +

Table value function returning the first name, last name, job title and contact type for a given contact.

+ + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDescription
@ContactIDint10,0Input parameter for the table value function ufnGetContactInformation. Enter a valid ContactID from the Person.Contact table.
+ + + + + + + + + + + + + + + + + +
Column NameSystem Data TypeAllow Null
ContactIDintNo
FirstNamenvarcharYes
LastNamenvarcharYes
JobTitlenvarcharYes
ContactTypenvarcharYes

+ guest (schema) +

+ + + + + + + + + + + + + + + + + +
Object TypeCount
Tables0
Views0
Stored procedures0
Scalar functions0
Table functions0

+ HumanResources (schema) +

Contains objects related to employees and departments.

+ + + + + + + + + + + + + + + + + +
Object TypeCount
Tables7
Views6
Stored procedures3
Scalar functions0
Table functions0

+ HumanResources.Department (table) +

+ + + + + + + + + + + + + + + +
+ Lookup table containing the departments within the Adventure Works Cycles company. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
DepartmentIDsmallint (identity)5,0NononeNoPrimary key for Department records.
Namenvarchar50NononeNoName of the department.
GroupNamenvarchar50NononeNoName of the group to which the department belongs.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on Department +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Department_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_Department_DepartmentIDClustered index created by a primary key constraint.YesYesCLUSTEREDDepartmentID

+ HumanResources.Employee (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Employee information such as salary, department, and title. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
EmployeeIDint (identity)10,0NononeNoPrimary key for Employee records.
NationalIDNumbernvarchar15NononeNoUnique national identification number such as a social security number.
ContactIDint10,0NononeNoIdentifies the employee in the Contact table. Foreign key to Contact.ContactID.
LoginIDnvarchar256NononeNoNetwork login.
ManagerIDint10,0YesnoneNoManager to whom the employee is assigned. Foreign Key to Employee.M
Titlenvarchar50NononeNoWork title such as Buyer or Sales Representative.
BirthDatedatetimeNononeNoDate of birth.
MaritalStatusnchar1NononeNoM = Married, S = Single
Gendernchar1NononeNoM = Male, F = Female
HireDatedatetimeNononeNoEmployee hired on this date.
SalariedFlagbitNo((1))NoJob classification. 0 = Hourly, not exempt from collective bargaining. 1 = Salaried, exempt from collective bargaining.
VacationHourssmallint5,0No((0))NoNumber of available vacation hours.
SickLeaveHourssmallint5,0No((0))NoNumber of available sick leave hours.
CurrentFlagbitNo((1))No0 = Inactive, 1 = Active
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + + + +
+ Indexes on Employee +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Employee_LoginIDUnique nonclustered index.NoYesNONCLUSTEREDLoginID
AK_Employee_NationalIDNumberUnique nonclustered index.NoYesNONCLUSTEREDNationalIDNumber
AK_Employee_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_Employee_ManagerIDNonclustered index.NoNoNONCLUSTEREDManagerID
PK_Employee_EmployeeIDClustered index created by a primary key constraint.YesYesCLUSTEREDEmployeeID
+ + + + + + + + + +
+ Foreign Keys in Employee +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_Employee_Contact_ContactIDContactIDPerson.Contact (ContactID)Foreign key constraint referencing Contact.ContactID.
FK_Employee_Employee_ManagerIDManagerIDHumanResources.Employee (EmployeeID)Foreign key constraint referencing Employee.ManagerID.

+ HumanResources.EmployeeAddress (table) +

+ + + + + + + + + + + + + + + +
+ Cross-reference table mapping employees to their address(es). +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
EmployeeIDint10,0NononeNoPrimary key. Foreign key to Employee.EmployeeID.
AddressIDint10,0NononeNoPrimary key. Foreign key to Address.AddressID.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on EmployeeAddress +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_EmployeeAddress_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_EmployeeAddress_EmployeeID_AddressIDClustered index created by a primary key constraint.YesYesCLUSTEREDEmployeeID, AddressID
+ + + + + + + + + +
+ Foreign Keys in EmployeeAddress +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_EmployeeAddress_Address_AddressIDAddressIDPerson.Address (AddressID)Foreign key constraint referencing Address.AddressID.
FK_EmployeeAddress_Employee_EmployeeIDEmployeeIDHumanResources.Employee (EmployeeID)Foreign key constraint referencing Employee.EmployeeID.

+ HumanResources.EmployeeDepartmentHistory (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Employee department transfers. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
EmployeeIDint10,0NononeNoEmployee identification number. Foreign key to Employee.EmployeeID.
DepartmentIDsmallint5,0NononeNoDepartment in which the employee worked including currently. Foreign key to Department.DepartmentID.
ShiftIDtinyint3,0NononeNoIdentifies which 8-hour shift the employee works. Foreign key to Shift.Shift.ID.
StartDatedatetimeNononeNoDate the employee started work in the department.
EndDatedatetimeYesnoneNoDate the employee left the department. NULL = Current department.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on EmployeeDepartmentHistory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_EmployeeDepartmentHistory_DepartmentIDNonclustered index.NoNoNONCLUSTEREDDepartmentID
IX_EmployeeDepartmentHistory_ShiftIDNonclustered index.NoNoNONCLUSTEREDShiftID
PK_EmployeeDepartmentHistory_EmployeeID_StartDate_DepartmentIDClustered index created by a primary key constraint.YesYesCLUSTEREDEmployeeID, StartDate, DepartmentID, ShiftID
+ + + + + + + + + + + +
+ Foreign Keys in EmployeeDepartmentHistory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_EmployeeDepartmentHistory_Department_DepartmentIDDepartmentIDHumanResources.Department (DepartmentID)Foreign key constraint referencing Department.DepartmentID.
FK_EmployeeDepartmentHistory_Employee_EmployeeIDEmployeeIDHumanResources.Employee (EmployeeID)Foreign key constraint referencing Employee.EmployeeID.
FK_EmployeeDepartmentHistory_Shift_ShiftIDShiftIDHumanResources.Shift (ShiftID)Foreign key constraint referencing Shift.ShiftID

+ HumanResources.EmployeePayHistory (table) +

+ + + + + + + + + + + + + + + + + +
+ Employee pay history. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
EmployeeIDint10,0NononeNoEmployee identification number. Foreign key to Employee.EmployeeID.
RateChangeDatedatetimeNononeNoDate the change in pay is effective
Ratemoney19,4NononeNoSalary hourly rate.
PayFrequencytinyint3,0NononeNo1 = Salary received monthly, 2 = Salary received biweekly
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on EmployeePayHistory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_EmployeePayHistory_EmployeeID_RateChangeDateClustered index created by a primary key constraint.YesYesCLUSTEREDEmployeeID, RateChangeDate
+ + + + + + + +
+ Foreign Keys in EmployeePayHistory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_EmployeePayHistory_Employee_EmployeeIDEmployeeIDHumanResources.Employee (EmployeeID)Foreign key constraint referencing Employee.EmployeeID.

+ HumanResources.JobCandidate (table) +

+ + + + + + + + + + + + + + + +
+ Résumés submitted to Human Resources by job applicants. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
JobCandidateIDint (identity)10,0NononeNoPrimary key for JobCandidate records.
EmployeeIDint10,0YesnoneNoEmployee identification number if applicant was hired. Foreign key to Employee.EmployeeID.
ResumexmlYesnoneNoRésumé in XML format.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on JobCandidate +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_JobCandidate_EmployeeIDNonclustered index.NoNoNONCLUSTEREDEmployeeID
PK_JobCandidate_JobCandidateIDClustered index created by a primary key constraint.YesYesCLUSTEREDJobCandidateID
+ + + + + + + +
+ Foreign Keys in JobCandidate +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_JobCandidate_Employee_EmployeeIDEmployeeIDHumanResources.Employee (EmployeeID)Foreign key constraint referencing Employee.EmployeeID.

+ HumanResources.Shift (table) +

+ + + + + + + + + + + + + + + + + +
+ Work shift lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ShiftIDtinyint (identity)3,0NononeNoPrimary key for Shift records.
Namenvarchar50NononeNoShift description.
StartTimedatetimeNononeNoShift start time.
EndTimedatetimeNononeNoShift end time.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on Shift +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Shift_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_Shift_StartTime_EndTimeUnique nonclustered index.NoYesNONCLUSTEREDStartTime, EndTime
PK_Shift_ShiftIDClustered index created by a primary key constraint.YesYesCLUSTEREDShiftID

+ HumanResources.vEmployee (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Employee names and addresses. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
EmployeeIDint10,0NononeNo
Titlenvarchar8YesnoneNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
Suffixnvarchar10YesnoneNo
JobTitlenvarchar50NononeNo
Phonenvarchar25YesnoneNo
EmailAddressnvarchar50YesnoneNo
EmailPromotionint10,0NononeNo
AddressLine1nvarchar60NononeNo
AddressLine2nvarchar60YesnoneNo
Citynvarchar30NononeNo
StateProvinceNamenvarchar50NononeNo
PostalCodenvarchar15NononeNo
CountryRegionNamenvarchar50NononeNo
AdditionalContactInfoxmlYesnoneNo

+ HumanResources.vEmployeeDepartment (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Returns employee name, title, and current department. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
EmployeeIDint10,0NononeNo
Titlenvarchar8YesnoneNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
Suffixnvarchar10YesnoneNo
JobTitlenvarchar50NononeNo
Departmentnvarchar50NononeNo
GroupNamenvarchar50NononeNo
StartDatedatetimeNononeNo

+ HumanResources.vEmployeeDepartmentHistory (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Returns employee name and current and previous departments. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
EmployeeIDint10,0NononeNo
Titlenvarchar8YesnoneNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
Suffixnvarchar10YesnoneNo
Shiftnvarchar50NononeNo
Departmentnvarchar50NononeNo
GroupNamenvarchar50NononeNo
StartDatedatetimeNononeNo
EndDatedatetimeYesnoneNo

+ HumanResources.vJobCandidate (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Job candidate names and resumes. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
JobCandidateIDint (identity)10,0NononeNo
EmployeeIDint10,0YesnoneNo
Name.Prefixnvarchar30YesnoneNo
Name.Firstnvarchar30YesnoneNo
Name.Middlenvarchar30YesnoneNo
Name.Lastnvarchar30YesnoneNo
Name.Suffixnvarchar30YesnoneNo
SkillsnvarcharYesnoneNo
Addr.Typenvarchar30YesnoneNo
Addr.Loc.CountryRegionnvarchar100YesnoneNo
Addr.Loc.Statenvarchar100YesnoneNo
Addr.Loc.Citynvarchar100YesnoneNo
Addr.PostalCodenvarchar20YesnoneNo
EMailnvarcharYesnoneNo
WebSitenvarcharYesnoneNo
ModifiedDatedatetimeNononeNo

+ HumanResources.vJobCandidateEducation (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Displays the content from each education related element in the xml column Resume in the HumanResources.JobCandidate table. The content has been localized into French, Simplified Chinese and Thai. Some data may not display correctly unless supplemental language support is installed. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
JobCandidateIDint (identity)10,0NononeNo
Edu.LevelnvarcharYesnoneNo
Edu.StartDatedatetimeYesnoneNo
Edu.EndDatedatetimeYesnoneNo
Edu.Degreenvarchar50YesnoneNo
Edu.Majornvarchar50YesnoneNo
Edu.Minornvarchar50YesnoneNo
Edu.GPAnvarchar5YesnoneNo
Edu.GPAScalenvarchar5YesnoneNo
Edu.Schoolnvarchar100YesnoneNo
Edu.Loc.CountryRegionnvarchar100YesnoneNo
Edu.Loc.Statenvarchar100YesnoneNo
Edu.Loc.Citynvarchar100YesnoneNo

+ HumanResources.vJobCandidateEmployment (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Displays the content from each employement history related element in the xml column Resume in the HumanResources.JobCandidate table. The content has been localized into French, Simplified Chinese and Thai. Some data may not display correctly unless supplemental language support is installed. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
JobCandidateIDint (identity)10,0NononeNo
Emp.StartDatedatetimeYesnoneNo
Emp.EndDatedatetimeYesnoneNo
Emp.OrgNamenvarchar100YesnoneNo
Emp.JobTitlenvarchar100YesnoneNo
Emp.ResponsibilitynvarcharYesnoneNo
Emp.FunctionCategorynvarcharYesnoneNo
Emp.IndustryCategorynvarcharYesnoneNo
Emp.Loc.CountryRegionnvarcharYesnoneNo
Emp.Loc.StatenvarcharYesnoneNo
Emp.Loc.CitynvarcharYesnoneNo

+ HumanResources.uspUpdateEmployeeHireInfo (stored procedure) +

Updates the Employee table and inserts a new row in the EmployeePayHistory table with the values specified in the input parameters.

+ + + + + + + + + + + + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@EmployeeIDint10,0InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a valid EmployeeID from the Employee table.
@Titlenvarchar50InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a title for the employee.
@HireDatedatetimeInputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a hire date for the employee.
@RateChangeDatedatetimeInputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter the date the rate changed for the employee.
@Ratemoney19,4InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter the new rate for the employee.
@PayFrequencytinyint3,0InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter the pay frequency for the employee.
@CurrentFlagbitInputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter the current flag for the employee.

+ HumanResources.uspUpdateEmployeeLogin (stored procedure) +

Updates the Employee table with the values specified in the input parameters for the given EmployeeID.

+ + + + + + + + + + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@EmployeeIDint10,0InputInput parameter for the stored procedure uspUpdateEmployeeLogin. Enter a valid EmployeeID from the Employee table.
@ManagerIDint10,0InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a valid ManagerID for the employee.
@LoginIDnvarchar256InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a valid login for the employee.
@Titlenvarchar50InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a title for the employee.
@HireDatedatetimeInputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a hire date for the employee.
@CurrentFlagbitInputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter the current flag for the employee.

+ HumanResources.uspUpdateEmployeePersonalInfo (stored procedure) +

Updates the Employee table with the values specified in the input parameters for the given EmployeeID.

+ + + + + + + + + + + + + + + + + +
+ Parameters +
Paramater NameSystem Data TypeSizeDirectionDescription
@EmployeeIDint10,0InputInput parameter for the stored procedure uspUpdateEmployeePersonalInfo. Enter a valid EmployeeID from the HumanResources.Employee table.
@NationalIDNumbernvarchar15InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a national ID for the employee.
@BirthDatedatetimeInputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a birth date for the employee.
@MaritalStatusnchar1InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a marital status for the employee.
@Gendernchar1InputInput parameter for the stored procedure uspUpdateEmployeeHireInfo. Enter a gender for the employee.

+ Person (schema) +

Contains objects related to names and addresses of customers, vendors, and employees

+ + + + + + + + + + + + + + + + + +
Object TypeCount
Tables6
Views2
Stored procedures0
Scalar functions0
Table functions0

+ Person.Address (table) +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ Street address information for customers, employees, and vendors. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
AddressIDint (identity)10,0NononeNoPrimary key for Address records.
AddressLine1nvarchar60NononeNoFirst street address line.
AddressLine2nvarchar60YesnoneNoSecond street address line.
Citynvarchar30NononeNoName of the city.
StateProvinceIDint10,0NononeNoUnique identification number for the state or province. Foreign key to StateProvince table.
PostalCodenvarchar15NononeNoPostal code for the street address.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + +
+ Indexes on Address +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Address_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_Address_AddressLine1_AddressLine2_City_StateProvinceID_PostalCodeNonclustered index.NoYesNONCLUSTEREDAddressLine1, AddressLine2, City, StateProvinceID, PostalCode
IX_Address_StateProvinceIDNonclustered index.NoNoNONCLUSTEREDStateProvinceID
PK_Address_AddressIDClustered index created by a primary key constraint.YesYesCLUSTEREDAddressID
+ + + + + + + +
+ Foreign Keys in Address +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_Address_StateProvince_StateProvinceIDStateProvinceIDPerson.StateProvince (StateProvinceID)Foreign key constraint referencing StateProvince.StateProvinceID.

+ Person.AddressType (table) +

+ + + + + + + + + + + + + + + +
+ Types of addresses stored in the Address table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
AddressTypeIDint (identity)10,0NononeNoPrimary key for AddressType records.
Namenvarchar50NononeNoAddress type description. For example, Billing, Home, or Shipping.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on AddressType +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_AddressType_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_AddressType_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_AddressType_AddressTypeIDClustered index created by a primary key constraint.YesYesCLUSTEREDAddressTypeID

+ Person.Contact (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Names of each employee, customer contact, and vendor contact. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ContactIDint (identity)10,0NononeNoPrimary key for Contact records.
NameStylebitNo((0))No0 = The data in FirstName and LastName are stored in western style (first name, last name) order. 1 = Eastern style (last name, first name) order.
Titlenvarchar8YesnoneNoA courtesy title. For example, Mr. or Ms.
FirstNamenvarchar50NononeNoFirst name of the person.
MiddleNamenvarchar50YesnoneNoMiddle name or middle initial of the person.
LastNamenvarchar50NononeNoLast name of the person.
Suffixnvarchar10YesnoneNoSurname suffix. For example, Sr. or Jr.
EmailAddressnvarchar50YesnoneNoE-mail address for the person.
EmailPromotionint10,0No((0))No0 = Contact does not wish to receive e-mail promotions, 1 = Contact does wish to receive e-mail promotions from AdventureWorks, 2 = Contact does wish to receive e-mail promotions from AdventureWorks and selected partners.
Phonenvarchar25YesnoneNoPhone number associated with the person.
PasswordHashvarchar128NononeNoPassword for the e-mail account.
PasswordSaltvarchar10NononeNoRandom value concatenated with the password string before the password is hashed.
AdditionalContactInfoxmlYesnoneNoAdditional contact information about the person stored in xml format.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + +
+ Indexes on Contact +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Contact_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_Contact_EmailAddressNonclustered index.NoNoNONCLUSTEREDEmailAddress
PK_Contact_ContactIDClustered index created by a primary key constraint.YesYesCLUSTEREDContactID
PXML_Contact_AddContactPrimary XML index.NoNoXMLAdditionalContactInfo

+ Person.ContactType (table) +

+ + + + + + + + + + + + + +
+ Lookup table containing the types of contacts stored in Contact. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ContactTypeIDint (identity)10,0NononeNoPrimary key for ContactType records.
Namenvarchar50NononeNoContact type description.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on ContactType +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_ContactType_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_ContactType_ContactTypeIDClustered index created by a primary key constraint.YesYesCLUSTEREDContactTypeID

+ Person.CountryRegion (table) +

+ + + + + + + + + + + + + +
+ Lookup table containing the ISO standard codes for countries and regions. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CountryRegionCodenvarchar3NononeNoISO standard code for countries and regions.
Namenvarchar50NononeNoCountry or region name.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on CountryRegion +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_CountryRegion_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_CountryRegion_CountryRegionCodeClustered index created by a primary key constraint.YesYesCLUSTEREDCountryRegionCode

+ Person.StateProvince (table) +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ State and province lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
StateProvinceIDint (identity)10,0NononeNoPrimary key for StateProvince records.
StateProvinceCodenchar3NononeNoISO standard state or province code.
CountryRegionCodenvarchar3NononeNoISO standard country or region code. Foreign key to CountryRegion.CountryRegionCode.
IsOnlyStateProvinceFlagbitNo((1))No0 = StateProvinceCode exists. 1 = StateProvinceCode unavailable, using CountryRegionCode.
Namenvarchar50NononeNoState or province description.
TerritoryIDint10,0NononeNoID of the territory in which the state or province is located. Foreign key to SalesTerritory.SalesTerritoryID.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + +
+ Indexes on StateProvince +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_StateProvince_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_StateProvince_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
AK_StateProvince_StateProvinceCode_CountryRegionCodeUnique nonclustered index.NoYesNONCLUSTEREDStateProvinceCode, CountryRegionCode
PK_StateProvince_StateProvinceIDClustered index created by a primary key constraint.YesYesCLUSTEREDStateProvinceID
+ + + + + + + + + +
+ Foreign Keys in StateProvince +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_StateProvince_CountryRegion_CountryRegionCodeCountryRegionCodePerson.CountryRegion (CountryRegionCode)Foreign key constraint referencing CountryRegion.CountryRegionCode.
FK_StateProvince_SalesTerritory_TerritoryIDTerritoryIDSales.SalesTerritory (TerritoryID)Foreign key constraint referencing SalesTerritory.TerritoryID.

+ Person.vAdditionalContactInfo (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Displays the contact name and content from each element in the xml column AdditionalContactInfo for that person. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
ContactIDint (identity)10,0NononeNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
TelephoneNumbernvarchar50YesnoneNo
TelephoneSpecialInstructionsnvarcharYesnoneNo
Streetnvarchar50YesnoneNo
Citynvarchar50YesnoneNo
StateProvincenvarchar50YesnoneNo
PostalCodenvarchar50YesnoneNo
CountryRegionnvarchar50YesnoneNo
HomeAddressSpecialInstructionsnvarcharYesnoneNo
EMailAddressnvarchar128YesnoneNo
EMailSpecialInstructionsnvarcharYesnoneNo
EMailTelephoneNumbernvarchar50YesnoneNo
rowguiduniqueidentifierNononeNo
ModifiedDatedatetimeNononeNo

+ Person.vStateProvinceCountryRegion (view) +

+ + + + + + + + + + + + + + + + + + + + + +
+ Joins StateProvince table with CountryRegion table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
StateProvinceIDint10,0NononeNo
StateProvinceCodenchar3NononeNo
IsOnlyStateProvinceFlagbitNononeNo
StateProvinceNamenvarchar50NononeNo
TerritoryIDint10,0NononeNo
CountryRegionCodenvarchar3NononeNo
CountryRegionNamenvarchar50NononeNo
+ + + + + + + + + +
+ Indexes on vStateProvinceCountryRegion +
Index NameDescriptionIs UniqueIndex TypeColumns
IX_vStateProvinceCountryRegionClustered index on the view vStateProvinceCountryRegion.YesCLUSTEREDStateProvinceID, CountryRegionCode

+ Production (schema) +

Contains objects related to products, inventory, and manufacturing.

+ + + + + + + + + + + + + + + + + +
Object TypeCount
Tables25
Views3
Stored procedures0
Scalar functions0
Table functions0

+ Production.BillOfMaterials (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Items required to make bicycles and bicycle subassemblies. It identifies the heirarchical relationship between a parent product and its components. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
BillOfMaterialsIDint (identity)10,0NononeNoPrimary key for BillOfMaterials records.
ProductAssemblyIDint10,0YesnoneNoParent product identification number. Foreign key to Product.ProductID.
ComponentIDint10,0NononeNoComponent identification number. Foreign key to Product.ProductID.
StartDatedatetimeNo(getdate())NoDate the component started being used in the assembly item.
EndDatedatetimeYesnoneNoDate the component stopped being used in the assembly item.
UnitMeasureCodenchar3NononeNoStandard code identifying the unit of measure for the quantity.
BOMLevelsmallint5,0NononeNoIndicates the depth the component is from its parent (AssemblyID).
PerAssemblyQtydecimal8,2No((1.00))NoQuantity of the component needed to create the assembly.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on BillOfMaterials +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_BillOfMaterials_ProductAssemblyID_ComponentID_StartDateClustered index.NoYesCLUSTEREDProductAssemblyID, ComponentID, StartDate
IX_BillOfMaterials_UnitMeasureCodeNonclustered index.NoNoNONCLUSTEREDUnitMeasureCode
PK_BillOfMaterials_BillOfMaterialsIDNonclustered index created by a primary key constraint.YesYesNONCLUSTEREDBillOfMaterialsID
+ + + + + + + + + + + +
+ Foreign Keys in BillOfMaterials +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_BillOfMaterials_Product_ComponentIDComponentIDProduction.Product (ProductID)Foreign key constraint referencing Product.ComponentID.
FK_BillOfMaterials_Product_ProductAssemblyIDProductAssemblyIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductAssemblyID.
FK_BillOfMaterials_UnitMeasure_UnitMeasureCodeUnitMeasureCodeProduction.UnitMeasure (UnitMeasureCode)Foreign key constraint referencing UnitMeasure.UnitMeasureCode.

+ Production.Culture (table) +

+ + + + + + + + + + + + + +
+ Lookup table containing the languages in which some AdventureWorks data is stored. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CultureIDnchar6NononeNoPrimary key for Culture records.
Namenvarchar50NononeNoCulture description.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on Culture +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Culture_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_Culture_CultureIDClustered index created by a primary key constraint.YesYesCLUSTEREDCultureID

+ Production.Document (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Product maintenance documents. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
DocumentIDint (identity)10,0NononeNoPrimary key for Document records.
Titlenvarchar50NononeNoTitle of the document.
FileNamenvarchar400NononeNoDirectory path and file name of the document
FileExtensionnvarchar8NononeNoFile extension indicating the document type. For example, .doc or .txt.
Revisionnchar5NononeNoRevision number of the document.
ChangeNumberint10,0No((0))NoEngineering change approval number.
Statustinyint3,0NononeNo1 = Pending approval, 2 = Approved, 3 = Obsolete
DocumentSummarynvarcharYesnoneNoDocument abstract.
DocumentvarbinaryYesnoneNoComplete document.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on Document +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Document_FileName_RevisionUnique nonclustered index.NoYesNONCLUSTEREDFileName, Revision
PK_Document_DocumentIDClustered index created by a primary key constraint.YesYesCLUSTEREDDocumentID

+ Production.Illustration (table) +

+ + + + + + + + + + + + + +
+ Bicycle assembly diagrams. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
IllustrationIDint (identity)10,0NononeNoPrimary key for Illustration records.
DiagramxmlYesnoneNoIllustrations used in manufacturing instructions. Stored as XML.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on Illustration +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_Illustration_IllustrationIDClustered index created by a primary key constraint.YesYesCLUSTEREDIllustrationID

+ Production.Location (table) +

+ + + + + + + + + + + + + + + + + +
+ Product inventory and manufacturing locations. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
LocationIDsmallint (identity)5,0NononeNoPrimary key for Location records.
Namenvarchar50NononeNoLocation description.
CostRatesmallmoney10,4No((0.00))NoStandard hourly cost of the manufacturing location.
Availabilitydecimal8,2No((0.00))NoWork capacity (in hours) of the manufacturing location.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on Location +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Location_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_Location_LocationIDClustered index created by a primary key constraint.YesYesCLUSTEREDLocationID

+ Production.Product (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Products sold or used in the manfacturing of sold products. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductIDint (identity)10,0NononeNoPrimary key for Product records.
Namenvarchar50NononeNoName of the product.
ProductNumbernvarchar25NononeNoUnique product identification number.
MakeFlagbitNo((1))No0 = Product is purchased, 1 = Product is manufactured in-house.
FinishedGoodsFlagbitNo((1))No0 = Product is not a salable item. 1 = Product is salable.
Colornvarchar15YesnoneNoProduct color.
SafetyStockLevelsmallint5,0NononeNoMinimum inventory quantity.
ReorderPointsmallint5,0NononeNoInventory level that triggers a purchase order or work order.
StandardCostmoney19,4NononeNoStandard cost of the product.
ListPricemoney19,4NononeNoSelling price.
Sizenvarchar5YesnoneNoProduct size.
SizeUnitMeasureCodenchar3YesnoneNoUnit of measure for Size column.
WeightUnitMeasureCodenchar3YesnoneNoUnit of measure for Weight column.
Weightdecimal8,2YesnoneNoProduct weight.
DaysToManufactureint10,0NononeNoNumber of days required to manufacture the product.
ProductLinenchar2YesnoneNoR = Road, M = Mountain, T = Touring, S = Standard
Classnchar2YesnoneNoH = High, M = Medium, L = Low
Stylenchar2YesnoneNoW = Womens, M = Mens, U = Universal
ProductSubcategoryIDint10,0YesnoneNoProduct is a member of this product subcategory. Foreign key to ProductSubCategory.ProductSubCategoryID.
ProductModelIDint10,0YesnoneNoProduct is a member of this product model. Foreign key to ProductModel.ProductModelID.
SellStartDatedatetimeNononeNoDate the product was available for sale.
SellEndDatedatetimeYesnoneNoDate the product was no longer available for sale.
DiscontinuedDatedatetimeYesnoneNoDate the product was discontinued.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + +
+ Indexes on Product +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Product_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_Product_ProductNumberUnique nonclustered index.NoYesNONCLUSTEREDProductNumber
AK_Product_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_Product_ProductIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductID
+ + + + + + + + + + + + + +
+ Foreign Keys in Product +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_Product_ProductModel_ProductModelIDProductModelIDProduction.ProductModel (ProductModelID)Foreign key constraint referencing ProductModel.ProductModelID.
FK_Product_ProductSubcategory_ProductSubcategoryIDProductSubcategoryIDProduction.ProductSubcategory (ProductSubcategoryID)Foreign key constraint referencing ProductSubcategory.ProductSubcategoryID.
FK_Product_UnitMeasure_SizeUnitMeasureCodeSizeUnitMeasureCodeProduction.UnitMeasure (UnitMeasureCode)Foreign key constraint referencing UnitMeasure.UnitMeasureCode.
FK_Product_UnitMeasure_WeightUnitMeasureCodeWeightUnitMeasureCodeProduction.UnitMeasure (UnitMeasureCode)Foreign key constraint referencing UnitMeasure.UnitMeasureCode.

+ Production.ProductCategory (table) +

+ + + + + + + + + + + + + + + +
+ High-level product categorization. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductCategoryIDint (identity)10,0NononeNoPrimary key for ProductCategory records.
Namenvarchar50NononeNoCategory description.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on ProductCategory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_ProductCategory_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_ProductCategory_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_ProductCategory_ProductCategoryIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductCategoryID

+ Production.ProductCostHistory (table) +

+ + + + + + + + + + + + + + + + + +
+ Changes in the cost of a product over time. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID
StartDatedatetimeNononeNoProduct cost start date.
EndDatedatetimeYesnoneNoProduct cost end date.
StandardCostmoney19,4NononeNoStandard cost of the product.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductCostHistory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductCostHistory_ProductID_StartDateClustered index created by a primary key constraint.YesYesCLUSTEREDProductID, StartDate
+ + + + + + + +
+ Foreign Keys in ProductCostHistory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductCostHistory_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.

+ Production.ProductDescription (table) +

+ + + + + + + + + + + + + + + +
+ Product descriptions in several languages. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductDescriptionIDint (identity)10,0NononeNoPrimary key for ProductDescription records.
Descriptionnvarchar400NononeNoDescription of the product.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on ProductDescription +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_ProductDescription_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_ProductDescription_ProductDescriptionIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductDescriptionID

+ Production.ProductDocument (table) +

+ + + + + + + + + + + + + +
+ Cross-reference table mapping products to related product documents. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
DocumentIDint10,0NononeNoDocument identification number. Foreign key to Document.DocumentID.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductDocument +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductDocument_ProductID_DocumentIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductID, DocumentID
+ + + + + + + + + +
+ Foreign Keys in ProductDocument +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductDocument_Document_DocumentIDDocumentIDProduction.Document (DocumentID)Foreign key constraint referencing Document.DocumentID.
FK_ProductDocument_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.

+ Production.ProductInventory (table) +

+ + + + + + + + + + + + + + + + + + + + + +
+ Product inventory information. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
LocationIDsmallint5,0NononeNoInventory location identification number. Foreign key to Location.LocationID.
Shelfnvarchar10NononeNoStorage compartment within an inventory location.
Bintinyint3,0NononeNoStorage container on a shelf in an inventory location.
Quantitysmallint5,0No((0))NoQuantity of products in the inventory location.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductInventory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductInventory_ProductID_LocationIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductID, LocationID
+ + + + + + + + + +
+ Foreign Keys in ProductInventory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductInventory_Location_LocationIDLocationIDProduction.Location (LocationID)Foreign key constraint referencing Location.LocationID.
FK_ProductInventory_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.

+ Production.ProductListPriceHistory (table) +

+ + + + + + + + + + + + + + + + + +
+ Changes in the list price of a product over time. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID
StartDatedatetimeNononeNoList price start date.
EndDatedatetimeYesnoneNoList price end date
ListPricemoney19,4NononeNoProduct list price.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductListPriceHistory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductListPriceHistory_ProductID_StartDateClustered index created by a primary key constraint.YesYesCLUSTEREDProductID, StartDate
+ + + + + + + +
+ Foreign Keys in ProductListPriceHistory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductListPriceHistory_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.

+ Production.ProductModel (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Product model classification. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductModelIDint (identity)10,0NononeNoPrimary key for ProductModel records.
Namenvarchar50NononeNoProduct model description.
CatalogDescriptionxmlYesnoneNoDetailed product catalog information in xml format.
InstructionsxmlYesnoneNoManufacturing instructions in xml format.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + + + +
+ Indexes on ProductModel +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_ProductModel_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_ProductModel_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_ProductModel_ProductModelIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductModelID
PXML_ProductModel_CatalogDescriptionPrimary XML index.NoNoXMLCatalogDescription
PXML_ProductModel_InstructionsPrimary XML index.NoNoXMLInstructions

+ Production.ProductModelIllustration (table) +

+ + + + + + + + + + + + + +
+ Cross-reference table mapping product models and illustrations. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductModelIDint10,0NononeNoPrimary key. Foreign key to ProductModel.ProductModelID.
IllustrationIDint10,0NononeNoPrimary key. Foreign key to Illustration.IllustrationID.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductModelIllustration +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductModelIllustration_ProductModelID_IllustrationIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductModelID, IllustrationID
+ + + + + + + + + +
+ Foreign Keys in ProductModelIllustration +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductModelIllustration_Illustration_IllustrationIDIllustrationIDProduction.Illustration (IllustrationID)Foreign key constraint referencing Illustration.IllustrationID.
FK_ProductModelIllustration_ProductModel_ProductModelIDProductModelIDProduction.ProductModel (ProductModelID)Foreign key constraint referencing ProductModel.ProductModelID.

+ Production.ProductModelProductDescriptionCulture (table) +

+ + + + + + + + + + + + + + + +
+ Cross-reference table mapping product descriptions and the language the description is written in. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductModelIDint10,0NononeNoPrimary key. Foreign key to ProductModel.ProductModelID.
ProductDescriptionIDint10,0NononeNoPrimary key. Foreign key to ProductDescription.ProductDescriptionID.
CultureIDnchar6NononeNoCulture identification number. Foreign key to Culture.CultureID.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductModelProductDescriptionCulture +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductModelProductDescriptionCulture_ProductModelID_ProductDescriptionID_CultureIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductModelID, ProductDescriptionID, CultureID
+ + + + + + + + + + + +
+ Foreign Keys in ProductModelProductDescriptionCulture +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductModelProductDescriptionCulture_Culture_CultureIDCultureIDProduction.Culture (CultureID)Foreign key constraint referencing Culture.CultureID.
FK_ProductModelProductDescriptionCulture_ProductDescription_ProductDescriptionIDProductDescriptionIDProduction.ProductDescription (ProductDescriptionID)Foreign key constraint referencing ProductDescription.ProductDescriptionID.
FK_ProductModelProductDescriptionCulture_ProductModel_ProductModelIDProductModelIDProduction.ProductModel (ProductModelID)Foreign key constraint referencing ProductModel.ProductModelID.

+ Production.ProductPhoto (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Product images. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductPhotoIDint (identity)10,0NononeNoPrimary key for ProductPhoto records.
ThumbNailPhotovarbinaryYesnoneNoSmall image of the product.
ThumbnailPhotoFileNamenvarchar50YesnoneNoSmall image file name.
LargePhotovarbinaryYesnoneNoLarge image of the product.
LargePhotoFileNamenvarchar50YesnoneNoLarge image file name.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductPhoto +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductPhoto_ProductPhotoIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductPhotoID

+ Production.ProductProductPhoto (table) +

+ + + + + + + + + + + + + + + +
+ Cross-reference table mapping products and product photos. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
ProductPhotoIDint10,0NononeNoProduct photo identification number. Foreign key to ProductPhoto.ProductPhotoID.
PrimarybitNo((0))No0 = Photo is not the principal image. 1 = Photo is the principal image.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ProductProductPhoto +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ProductProductPhoto_ProductID_ProductPhotoIDNonclustered index created by a primary key constraint.YesYesNONCLUSTEREDProductID, ProductPhotoID
+ + + + + + + + + +
+ Foreign Keys in ProductProductPhoto +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductProductPhoto_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.
FK_ProductProductPhoto_ProductPhoto_ProductPhotoIDProductPhotoIDProduction.ProductPhoto (ProductPhotoID)Foreign key constraint referencing ProductPhoto.ProductPhotoID.

+ Production.ProductReview (table) +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ Customer reviews of products they have purchased. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductReviewIDint (identity)10,0NononeNoPrimary key for ProductReview records.
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
ReviewerNamenvarchar50NononeNoName of the reviewer.
ReviewDatedatetimeNo(getdate())NoDate review was submitted.
EmailAddressnvarchar50NononeNoReviewer's e-mail address.
Ratingint10,0NononeNoProduct rating given by the reviewer. Scale is 1 to 5 with 5 as the highest rating.
Commentsnvarchar3850YesnoneNoReviewer's comments
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on ProductReview +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_ProductReview_ProductID_NameNonclustered index.NoNoNONCLUSTEREDComments, ProductID, ReviewerName
PK_ProductReview_ProductReviewIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductReviewID
+ + + + + + + +
+ Foreign Keys in ProductReview +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductReview_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.

+ Production.ProductSubcategory (table) +

+ + + + + + + + + + + + + + + + + +
+ Product subcategories. See ProductCategory table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductSubcategoryIDint (identity)10,0NononeNoPrimary key for ProductSubcategory records.
ProductCategoryIDint10,0NononeNoProduct category identification number. Foreign key to ProductCategory.ProductCategoryID.
Namenvarchar50NononeNoSubcategory description.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on ProductSubcategory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_ProductSubcategory_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_ProductSubcategory_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_ProductSubcategory_ProductSubcategoryIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductSubcategoryID
+ + + + + + + +
+ Foreign Keys in ProductSubcategory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductSubcategory_ProductCategory_ProductCategoryIDProductCategoryIDProduction.ProductCategory (ProductCategoryID)Foreign key constraint referencing ProductCategory.ProductCategoryID.

+ Production.ScrapReason (table) +

+ + + + + + + + + + + + + +
+ Manufacturing failure reasons lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ScrapReasonIDsmallint (identity)5,0NononeNoPrimary key for ScrapReason records.
Namenvarchar50NononeNoFailure description.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on ScrapReason +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_ScrapReason_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_ScrapReason_ScrapReasonIDClustered index created by a primary key constraint.YesYesCLUSTEREDScrapReasonID

+ Production.TransactionHistory (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Record of each purchase order, sales order, or work order transaction year to date. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
TransactionIDint (identity)10,0NononeNoPrimary key for TransactionHistory records.
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
ReferenceOrderIDint10,0NononeNoPurchase order, sales order, or work order identification number.
ReferenceOrderLineIDint10,0No((0))NoLine number associated with the purchase order, sales order, or work order.
TransactionDatedatetimeNo(getdate())NoDate and time of the transaction.
TransactionTypenchar1NononeNoW = WorkOrder, S = SalesOrder, P = PurchaseOrder
Quantityint10,0NononeNoProduct quantity.
ActualCostmoney19,4NononeNoProduct cost.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on TransactionHistory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_TransactionHistory_ProductIDNonclustered index.NoNoNONCLUSTEREDProductID
IX_TransactionHistory_ReferenceOrderID_ReferenceOrderLineIDNonclustered index.NoNoNONCLUSTEREDReferenceOrderID, ReferenceOrderLineID
PK_TransactionHistory_TransactionIDClustered index created by a primary key constraint.YesYesCLUSTEREDTransactionID
+ + + + + + + +
+ Foreign Keys in TransactionHistory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_TransactionHistory_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.

+ Production.TransactionHistoryArchive (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Transactions for previous years. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
TransactionIDint10,0NononeNoPrimary key for TransactionHistoryArchive records.
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
ReferenceOrderIDint10,0NononeNoPurchase order, sales order, or work order identification number.
ReferenceOrderLineIDint10,0No((0))NoLine number associated with the purchase order, sales order, or work order.
TransactionDatedatetimeNo(getdate())NoDate and time of the transaction.
TransactionTypenchar1NononeNoW = Work Order, S = Sales Order, P = Purchase Order
Quantityint10,0NononeNoProduct quantity.
ActualCostmoney19,4NononeNoProduct cost.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on TransactionHistoryArchive +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_TransactionHistoryArchive_ProductIDNonclustered index.NoNoNONCLUSTEREDProductID
IX_TransactionHistoryArchive_ReferenceOrderID_ReferenceOrderLineIDNonclustered index.NoNoNONCLUSTEREDReferenceOrderID, ReferenceOrderLineID
PK_TransactionHistoryArchive_TransactionIDClustered index created by a primary key constraint.YesYesCLUSTEREDTransactionID

+ Production.UnitMeasure (table) +

+ + + + + + + + + + + + + +
+ Unit of measure lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
UnitMeasureCodenchar3NononeNoPrimary key.
Namenvarchar50NononeNoUnit of measure description.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on UnitMeasure +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_UnitMeasure_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_UnitMeasure_UnitMeasureCodeClustered index created by a primary key constraint.YesYesCLUSTEREDUnitMeasureCode

+ Production.WorkOrder (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Manufacturing work orders. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
WorkOrderIDint (identity)10,0NononeNoPrimary key for WorkOrder records.
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
OrderQtyint10,0NononeNoProduct quantity to build.
StockedQtyint10,0NononeYesQuantity built and put in inventory.
ScrappedQtysmallint5,0NononeNoQuantity that failed inspection.
StartDatedatetimeNononeNoWork order start date.
EndDatedatetimeYesnoneNoWork order end date.
DueDatedatetimeNononeNoWork order due date.
ScrapReasonIDsmallint5,0YesnoneNoReason for inspection failure.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on WorkOrder +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_WorkOrder_ProductIDNonclustered index.NoNoNONCLUSTEREDProductID
IX_WorkOrder_ScrapReasonIDNonclustered index.NoNoNONCLUSTEREDScrapReasonID
PK_WorkOrder_WorkOrderIDClustered index created by a primary key constraint.YesYesCLUSTEREDWorkOrderID
+ + + + + + + + + +
+ Foreign Keys in WorkOrder +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_WorkOrder_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.
FK_WorkOrder_ScrapReason_ScrapReasonIDScrapReasonIDProduction.ScrapReason (ScrapReasonID)Foreign key constraint referencing ScrapReason.ScrapReasonID.

+ Production.WorkOrderRouting (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Work order details. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
WorkOrderIDint10,0NononeNoPrimary key. Foreign key to WorkOrder.WorkOrderID.
ProductIDint10,0NononeNoPrimary key. Foreign key to Product.ProductID.
OperationSequencesmallint5,0NononeNoPrimary key. Indicates the manufacturing process sequence.
LocationIDsmallint5,0NononeNoManufacturing location where the part is processed. Foreign key to Location.LocationID.
ScheduledStartDatedatetimeNononeNoPlanned manufacturing start date.
ScheduledEndDatedatetimeNononeNoPlanned manufacturing end date.
ActualStartDatedatetimeYesnoneNoActual start date.
ActualEndDatedatetimeYesnoneNoActual end date.
ActualResourceHrsdecimal9,4YesnoneNoNumber of manufacturing hours used.
PlannedCostmoney19,4NononeNoEstimated manufacturing cost.
ActualCostmoney19,4YesnoneNoActual manufacturing cost.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on WorkOrderRouting +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_WorkOrderRouting_ProductIDNonclustered index.NoNoNONCLUSTEREDProductID
PK_WorkOrderRouting_WorkOrderID_ProductID_OperationSequenceClustered index created by a primary key constraint.YesYesCLUSTEREDWorkOrderID, ProductID, OperationSequence
+ + + + + + + + + +
+ Foreign Keys in WorkOrderRouting +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_WorkOrderRouting_Location_LocationIDLocationIDProduction.Location (LocationID)Foreign key constraint referencing Location.LocationID.
FK_WorkOrderRouting_WorkOrder_WorkOrderIDWorkOrderIDProduction.WorkOrder (WorkOrderID)Foreign key constraint referencing WorkOrder.WorkOrderID.

+ Production.vProductAndDescription (view) +

+ + + + + + + + + + + + + + + + + +
+ Product names and descriptions. Product descriptions are provided in multiple languages. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
ProductIDint10,0NononeNo
Namenvarchar50NononeNo
ProductModelnvarchar50NononeNo
CultureIDnchar6NononeNo
Descriptionnvarchar400NononeNo
+ + + + + + + + + +
+ Indexes on vProductAndDescription +
Index NameDescriptionIs UniqueIndex TypeColumns
IX_vProductAndDescriptionClustered index on the view vProductAndDescription.YesCLUSTEREDCultureID, ProductID

+ Production.vProductModelCatalogDescription (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Displays the content from each element in the xml column CatalogDescription for each product in the Production.ProductModel table that has catalog data. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
ProductModelIDint (identity)10,0NononeNo
Namenvarchar50NononeNo
SummarynvarcharYesnoneNo
ManufacturernvarcharYesnoneNo
Copyrightnvarchar30YesnoneNo
ProductURLnvarchar256YesnoneNo
WarrantyPeriodnvarchar256YesnoneNo
WarrantyDescriptionnvarchar256YesnoneNo
NoOfYearsnvarchar256YesnoneNo
MaintenanceDescriptionnvarchar256YesnoneNo
Wheelnvarchar256YesnoneNo
Saddlenvarchar256YesnoneNo
Pedalnvarchar256YesnoneNo
BikeFramenvarcharYesnoneNo
Cranksetnvarchar256YesnoneNo
PictureAnglenvarchar256YesnoneNo
PictureSizenvarchar256YesnoneNo
ProductPhotoIDnvarchar256YesnoneNo
Materialnvarchar256YesnoneNo
Colornvarchar256YesnoneNo
ProductLinenvarchar256YesnoneNo
Stylenvarchar256YesnoneNo
RiderExperiencenvarchar1024YesnoneNo
rowguiduniqueidentifierNononeNo
ModifiedDatedatetimeNononeNo

+ Production.vProductModelInstructions (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Displays the content from each element in the xml column Instructions for each product in the Production.ProductModel table that has manufacturing instructions. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
ProductModelIDint (identity)10,0NononeNo
Namenvarchar50NononeNo
InstructionsnvarcharYesnoneNo
LocationIDint10,0YesnoneNo
SetupHoursdecimal9,4YesnoneNo
MachineHoursdecimal9,4YesnoneNo
LaborHoursdecimal9,4YesnoneNo
LotSizeint10,0YesnoneNo
Stepnvarchar1024YesnoneNo
rowguiduniqueidentifierNononeNo
ModifiedDatedatetimeNononeNo

+ Purchasing (schema) +

Contains objects related to vendors and purchase orders.

+ + + + + + + + + + + + + + + + + +
Object TypeCount
Tables7
Views1
Stored procedures0
Scalar functions0
Table functions0

+ Purchasing.ProductVendor (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Cross-reference table mapping vendors with the products they supply. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ProductIDint10,0NononeNoPrimary key. Foreign key to Product.ProductID.
VendorIDint10,0NononeNoPrimary key. Foreign key to Vendor.VendorID.
AverageLeadTimeint10,0NononeNoThe average span of time (in days) between placing an order with the vendor and receiving the purchased product.
StandardPricemoney19,4NononeNoThe vendor's usual selling price.
LastReceiptCostmoney19,4YesnoneNoThe selling price when last purchased.
LastReceiptDatedatetimeYesnoneNoDate the product was last received by the vendor.
MinOrderQtyint10,0NononeNoThe maximum quantity that should be ordered.
MaxOrderQtyint10,0NononeNoThe minimum quantity that should be ordered.
OnOrderQtyint10,0YesnoneNoThe quantity currently on order.
UnitMeasureCodenchar3NononeNoThe product's unit of measure.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on ProductVendor +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_ProductVendor_UnitMeasureCodeNonclustered index.NoNoNONCLUSTEREDUnitMeasureCode
IX_ProductVendor_VendorIDNonclustered index.NoNoNONCLUSTEREDVendorID
PK_ProductVendor_ProductID_VendorIDClustered index created by a primary key constraint.YesYesCLUSTEREDProductID, VendorID
+ + + + + + + + + + + +
+ Foreign Keys in ProductVendor +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ProductVendor_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.
FK_ProductVendor_UnitMeasure_UnitMeasureCodeUnitMeasureCodeProduction.UnitMeasure (UnitMeasureCode)Foreign key constraint referencing UnitMeasure.UnitMeasureCode.
FK_ProductVendor_Vendor_VendorIDVendorIDPurchasing.Vendor (VendorID)Foreign key constraint referencing Vendor.VendorID.

+ Purchasing.PurchaseOrderDetail (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Individual products associated with a specific purchase order. See PurchaseOrderHeader. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
PurchaseOrderIDint10,0NononeNoPrimary key. Foreign key to PurchaseOrderHeader.PurchaseOrderID.
PurchaseOrderDetailIDint (identity)10,0NononeNoPrimary key. One line number per purchased product.
DueDatedatetimeNononeNoDate the product is expected to be received.
OrderQtysmallint5,0NononeNoQuantity ordered.
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
UnitPricemoney19,4NononeNoVendor's selling price of a single product.
LineTotalmoney19,4NononeYesPer product subtotal. Computed as OrderQty * UnitPrice.
ReceivedQtydecimal8,2NononeNoQuantity actually received from the vendor.
RejectedQtydecimal8,2NononeNoQuantity rejected during inspection.
StockedQtydecimal9,2NononeYesQuantity accepted into inventory. Computed as ReceivedQty - RejectedQty.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on PurchaseOrderDetail +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_PurchaseOrderDetail_ProductIDNonclustered index.NoNoNONCLUSTEREDProductID
PK_PurchaseOrderDetail_PurchaseOrderID_PurchaseOrderDetailIDClustered index created by a primary key constraint.YesYesCLUSTEREDPurchaseOrderID, PurchaseOrderDetailID
+ + + + + + + + + +
+ Foreign Keys in PurchaseOrderDetail +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_PurchaseOrderDetail_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.
FK_PurchaseOrderDetail_PurchaseOrderHeader_PurchaseOrderIDPurchaseOrderIDPurchasing.PurchaseOrderHeader (PurchaseOrderID)Foreign key constraint referencing PurchaseOrderHeader.PurchaseOrderID.

+ Purchasing.PurchaseOrderHeader (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ General purchase order information. See PurchaseOrderDetail. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
PurchaseOrderIDint (identity)10,0NononeNoPrimary key.
RevisionNumbertinyint3,0No((0))NoIncremental number to track changes to the purchase order over time.
Statustinyint3,0No((1))NoOrder current status. 1 = Pending; 2 = Approved; 3 = Rejected; 4 = Complete
EmployeeIDint10,0NononeNoEmployee who created the purchase order. Foreign key to Employee.EmployeeID.
VendorIDint10,0NononeNoVendor with whom the purchase order is placed. Foreign key to Vendor.VendorID.
ShipMethodIDint10,0NononeNoShipping method. Foreign key to ShipMethod.ShipMethodID.
OrderDatedatetimeNo(getdate())NoPurchase order creation date.
ShipDatedatetimeYesnoneNoEstimated shipment date from the vendor.
SubTotalmoney19,4No((0.00))NoPurchase order subtotal. Computed as SUM(PurchaseOrderDetail.LineTotal)for the appropriate PurchaseOrderID.
TaxAmtmoney19,4No((0.00))NoTax amount.
Freightmoney19,4No((0.00))NoShipping cost.
TotalDuemoney19,4NononeYesTotal due to vendor. Computed as Subtotal + TaxAmt + Freight.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on PurchaseOrderHeader +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_PurchaseOrderHeader_EmployeeIDNonclustered index.NoNoNONCLUSTEREDEmployeeID
IX_PurchaseOrderHeader_VendorIDNonclustered index.NoNoNONCLUSTEREDVendorID
PK_PurchaseOrderHeader_PurchaseOrderIDClustered index created by a primary key constraint.YesYesCLUSTEREDPurchaseOrderID
+ + + + + + + + + + + +
+ Foreign Keys in PurchaseOrderHeader +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_PurchaseOrderHeader_Employee_EmployeeIDEmployeeIDHumanResources.Employee (EmployeeID)Foreign key constraint referencing Employee.EmployeeID.
FK_PurchaseOrderHeader_ShipMethod_ShipMethodIDShipMethodIDPurchasing.ShipMethod (ShipMethodID)Foreign key constraint referencing ShipMethod.ShipMethodID.
FK_PurchaseOrderHeader_Vendor_VendorIDVendorIDPurchasing.Vendor (VendorID)Foreign key constraint referencing Vendor.VendorID.

+ Purchasing.ShipMethod (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Shipping company lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ShipMethodIDint (identity)10,0NononeNoPrimary key for ShipMethod records.
Namenvarchar50NononeNoShipping company name.
ShipBasemoney19,4No((0.00))NoMinimum shipping charge.
ShipRatemoney19,4No((0.00))NoShipping charge per pound.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on ShipMethod +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_ShipMethod_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_ShipMethod_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_ShipMethod_ShipMethodIDClustered index created by a primary key constraint.YesYesCLUSTEREDShipMethodID

+ Purchasing.Vendor (table) +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ Companies from whom Adventure Works Cycles purchases parts or other goods. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
VendorIDint (identity)10,0NononeNoPrimary key for Vendor records.
AccountNumbernvarchar15NononeNoVendor account (identification) number.
Namenvarchar50NononeNoCompany name.
CreditRatingtinyint3,0NononeNo1 = Superior, 2 = Excellent, 3 = Above average, 4 = Average, 5 = Below average
PreferredVendorStatusbitNo((1))No0 = Do not use if another vendor is available. 1 = Preferred over other vendors supplying the same product.
ActiveFlagbitNo((1))No0 = Vendor no longer used. 1 = Vendor is actively used.
PurchasingWebServiceURLnvarchar1024YesnoneNoVendor URL.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on Vendor +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Vendor_AccountNumberUnique nonclustered index.NoYesNONCLUSTEREDAccountNumber
PK_Vendor_VendorIDClustered index created by a primary key constraint.YesYesCLUSTEREDVendorID

+ Purchasing.VendorAddress (table) +

+ + + + + + + + + + + + + + + +
+ Cross-reference mapping vendors and addresses. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
VendorIDint10,0NononeNoPrimary key. Foreign key to Vendor.VendorID.
AddressIDint10,0NononeNoPrimary key. Foreign key to Address.AddressID.
AddressTypeIDint10,0NononeNoAddress type. Foreign key to AddressType.AddressTypeID.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on VendorAddress +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_VendorAddress_AddressIDNonclustered index.NoNoNONCLUSTEREDAddressID
PK_VendorAddress_VendorID_AddressIDClustered index created by a primary key constraint.YesYesCLUSTEREDVendorID, AddressID
+ + + + + + + + + + + +
+ Foreign Keys in VendorAddress +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_VendorAddress_Address_AddressIDAddressIDPerson.Address (AddressID)Foreign key constraint referencing Address.AddressID.
FK_VendorAddress_AddressType_AddressTypeIDAddressTypeIDPerson.AddressType (AddressTypeID)Foreign key constraint referencing AddressType.AddressTypeID.
FK_VendorAddress_Vendor_VendorIDVendorIDPurchasing.Vendor (VendorID)Foreign key constraint referencing Vendor.VendorID.

+ Purchasing.VendorContact (table) +

+ + + + + + + + + + + + + + + +
+ Cross-reference table mapping vendors and their employees. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
VendorIDint10,0NononeNoPrimary key.
ContactIDint10,0NononeNoContact (Vendor employee) identification number. Foreign key to Contact.ContactID.
ContactTypeIDint10,0NononeNoContact type such as sales manager, or sales agent.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on VendorContact +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_VendorContact_ContactIDNonclustered index.NoNoNONCLUSTEREDContactID
IX_VendorContact_ContactTypeIDNonclustered index.NoNoNONCLUSTEREDContactTypeID
PK_VendorContact_VendorID_ContactIDClustered index created by a primary key constraint.YesYesCLUSTEREDVendorID, ContactID
+ + + + + + + + + + + +
+ Foreign Keys in VendorContact +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_VendorContact_Contact_ContactIDContactIDPerson.Contact (ContactID)Foreign key constraint referencing Contact.ContactID.
FK_VendorContact_ContactType_ContactTypeIDContactTypeIDPerson.ContactType (ContactTypeID)Foreign key constraint referencing ContactType.ContactTypeID.
FK_VendorContact_Vendor_VendorIDVendorIDPurchasing.Vendor (VendorID)Foreign key constraint referencing Vendor.VendorID.

+ Purchasing.vVendor (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Vendor (company) names and addresses and the names of vendor employees to contact. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
VendorIDint10,0NononeNo
Namenvarchar50NononeNo
ContactTypenvarchar50NononeNo
Titlenvarchar8YesnoneNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
Suffixnvarchar10YesnoneNo
Phonenvarchar25YesnoneNo
EmailAddressnvarchar50YesnoneNo
EmailPromotionint10,0NononeNo
AddressLine1nvarchar60NononeNo
AddressLine2nvarchar60YesnoneNo
Citynvarchar30NononeNo
StateProvinceNamenvarchar50NononeNo
PostalCodenvarchar15NononeNo
CountryRegionNamenvarchar50NononeNo

+ Sales (schema) +

Contains objects related to customers, sales orders, and sales territories.

+ + + + + + + + + + + + + + + + + +
Object TypeCount
Tables22
Views5
Stored procedures0
Scalar functions0
Table functions0

+ Sales.ContactCreditCard (table) +

+ + + + + + + + + + + + + +
+ Cross-reference table mapping customers in the Contact table to their credit card information in the CreditCard table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ContactIDint10,0NononeNoCustomer identification number. Foreign key to Contact.ContactID.
CreditCardIDint10,0NononeNoCredit card identification number. Foreign key to CreditCard.CreditCardID.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on ContactCreditCard +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_ContactCreditCard_ContactID_CreditCardIDClustered index created by a primary key constraint.YesYesCLUSTEREDContactID, CreditCardID
+ + + + + + + + + +
+ Foreign Keys in ContactCreditCard +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ContactCreditCard_Contact_ContactIDContactIDPerson.Contact (ContactID)Foreign key constraint referencing Contact.ContactID.
FK_ContactCreditCard_CreditCard_CreditCardIDCreditCardIDSales.CreditCard (CreditCardID)Foreign key constraint referencing CreditCard.CreditCardID.

+ Sales.CountryRegionCurrency (table) +

+ + + + + + + + + + + + + +
+ Cross-reference table mapping ISO currency codes to a country or region. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CountryRegionCodenvarchar3NononeNoISO code for countries and regions. Foreign key to CountryRegion.CountryRegionCode.
CurrencyCodenchar3NononeNoISO standard currency code. Foreign key to Currency.CurrencyCode.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on CountryRegionCurrency +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_CountryRegionCurrency_CurrencyCodeNonclustered index.NoNoNONCLUSTEREDCurrencyCode
PK_CountryRegionCurrency_CountryRegionCode_CurrencyCodeClustered index created by a primary key constraint.YesYesCLUSTEREDCountryRegionCode, CurrencyCode
+ + + + + + + + + +
+ Foreign Keys in CountryRegionCurrency +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_CountryRegionCurrency_CountryRegion_CountryRegionCodeCountryRegionCodePerson.CountryRegion (CountryRegionCode)Foreign key constraint referencing CountryRegion.CountryRegionCode.
FK_CountryRegionCurrency_Currency_CurrencyCodeCurrencyCodeSales.Currency (CurrencyCode)Foreign key constraint referencing Currency.CurrencyCode.

+ Sales.CreditCard (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Customer credit card information. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CreditCardIDint (identity)10,0NononeNoPrimary key for CreditCard records.
CardTypenvarchar50NononeNoCredit card name.
CardNumbernvarchar25NononeNoCredit card number.
ExpMonthtinyint3,0NononeNoCredit card expiration month.
ExpYearsmallint5,0NononeNoCredit card expiration year.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on CreditCard +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_CreditCard_CardNumberUnique nonclustered index.NoYesNONCLUSTEREDCardNumber
PK_CreditCard_CreditCardIDClustered index created by a primary key constraint.YesYesCLUSTEREDCreditCardID

+ Sales.Currency (table) +

+ + + + + + + + + + + + + +
+ Lookup table containing standard ISO currencies. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CurrencyCodenchar3NononeNoThe ISO code for the Currency.
Namenvarchar50NononeNoCurrency name.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on Currency +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Currency_NameUnique nonclustered index.NoYesNONCLUSTEREDName
PK_Currency_CurrencyCodeClustered index created by a primary key constraint.YesYesCLUSTEREDCurrencyCode

+ Sales.CurrencyRate (table) +

+ + + + + + + + + + + + + + + + + + + + + +
+ Currency exchange rates. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CurrencyRateIDint (identity)10,0NononeNoPrimary key for CurrencyRate records.
CurrencyRateDatedatetimeNononeNoDate and time the exchange rate was obtained.
FromCurrencyCodenchar3NononeNoExchange rate was converted from this currency code.
ToCurrencyCodenchar3NononeNoExchange rate was converted to this currency code.
AverageRatemoney19,4NononeNoAverage exchange rate for the day.
EndOfDayRatemoney19,4NononeNoFinal exchange rate for the day.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on CurrencyRate +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_CurrencyRate_CurrencyRateDate_FromCurrencyCode_ToCurrencyCodeUnique nonclustered index.NoYesNONCLUSTEREDCurrencyRateDate, FromCurrencyCode, ToCurrencyCode
PK_CurrencyRate_CurrencyRateIDClustered index created by a primary key constraint.YesYesCLUSTEREDCurrencyRateID
+ + + + + + + + + +
+ Foreign Keys in CurrencyRate +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_CurrencyRate_Currency_FromCurrencyCodeFromCurrencyCodeSales.Currency (CurrencyCode)Foreign key constraint referencing Currency.FromCurrencyCode.
FK_CurrencyRate_Currency_ToCurrencyCodeToCurrencyCodeSales.Currency (CurrencyCode)Foreign key constraint referencing Currency.ToCurrencyCode.

+ Sales.Customer (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Current customer information. Also see the Individual and Store tables. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CustomerIDint (identity)10,0NononeNoPrimary key for Customer records.
TerritoryIDint10,0YesnoneNoID of the territory in which the customer is located. Foreign key to SalesTerritory.SalesTerritoryID.
AccountNumbervarchar10NononeYesUnique number identifying the customer assigned by the accounting system.
CustomerTypenchar1NononeNoCustomer type: I = Individual, S = Store
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + +
+ Indexes on Customer +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Customer_AccountNumberUnique nonclustered index.NoYesNONCLUSTEREDAccountNumber
AK_Customer_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_Customer_TerritoryIDNonclustered index.NoNoNONCLUSTEREDTerritoryID
PK_Customer_CustomerIDClustered index created by a primary key constraint.YesYesCLUSTEREDCustomerID
+ + + + + + + +
+ Foreign Keys in Customer +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_Customer_SalesTerritory_TerritoryIDTerritoryIDSales.SalesTerritory (TerritoryID)Foreign key constraint referencing SalesTerritory.TerritoryID.

+ Sales.CustomerAddress (table) +

+ + + + + + + + + + + + + + + + + +
+ Cross-reference table mapping customers to their address(es). +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CustomerIDint10,0NononeNoPrimary key. Foreign key to Customer.CustomerID.
AddressIDint10,0NononeNoPrimary key. Foreign key to Address.AddressID.
AddressTypeIDint10,0NononeNoAddress type. Foreign key to AddressType.AddressTypeID.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on CustomerAddress +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_CustomerAddress_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_CustomerAddress_CustomerID_AddressIDClustered index created by a primary key constraint.YesYesCLUSTEREDCustomerID, AddressID
+ + + + + + + + + + + +
+ Foreign Keys in CustomerAddress +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_CustomerAddress_Address_AddressIDAddressIDPerson.Address (AddressID)Foreign key constraint referencing Address.AddressID.
FK_CustomerAddress_AddressType_AddressTypeIDAddressTypeIDPerson.AddressType (AddressTypeID)Foreign key constraint referencing AddressType.AddressTypeID.
FK_CustomerAddress_Customer_CustomerIDCustomerIDSales.Customer (CustomerID)Foreign key constraint referencing Customer.CustomerID.

+ Sales.Individual (table) +

+ + + + + + + + + + + + + + + +
+ Demographic data about customers that purchase Adventure Works products online. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CustomerIDint10,0NononeNoUnique customer identification number. Foreign key to Customer.CustomerID.
ContactIDint10,0NononeNoIdentifies the customer in the Contact table. Foreign key to Contact.ContactID.
DemographicsxmlYesnoneNoPersonal information such as hobbies, and income collected from online shoppers. Used for sales analysis.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + + + +
+ Indexes on Individual +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_Individual_CustomerIDClustered index created by a primary key constraint.YesYesCLUSTEREDCustomerID
PXML_Individual_DemographicsPrimary XML index.NoNoXMLDemographics
XMLPATH_Individual_DemographicsSecondary XML index for path.NoNoXMLDemographics
XMLPROPERTY_Individual_DemographicsSecondary XML index for property.NoNoXMLDemographics
XMLVALUE_Individual_DemographicsSecondary XML index for value.NoNoXMLDemographics
+ + + + + + + + + +
+ Foreign Keys in Individual +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_Individual_Contact_ContactIDContactIDPerson.Contact (ContactID)Foreign key constraint referencing Contact.ContactID.
FK_Individual_Customer_CustomerIDCustomerIDSales.Customer (CustomerID)Foreign key constraint referencing Customer.CustomerID.

+ Sales.SalesOrderDetail (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Individual products associated with a specific sales order. See SalesOrderHeader. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesOrderIDint10,0NononeNoPrimary key. Foreign key to SalesOrderHeader.SalesOrderID.
SalesOrderDetailIDint (identity)10,0NononeNoPrimary key. One incremental unique number per product sold.
CarrierTrackingNumbernvarchar25YesnoneNoShipment tracking number supplied by the shipper.
OrderQtysmallint5,0NononeNoQuantity ordered per product.
ProductIDint10,0NononeNoProduct sold to customer. Foreign key to Product.ProductID.
SpecialOfferIDint10,0NononeNoPromotional code. Foreign key to SpecialOffer.SpecialOfferID.
UnitPricemoney19,4NononeNoSelling price of a single product.
UnitPriceDiscountmoney19,4No((0.0))NoDiscount amount.
LineTotalnumeric38,6NononeYesPer product subtotal. Computed as UnitPrice * (1 - UnitPriceDiscount) * OrderQty.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on SalesOrderDetail +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SalesOrderDetail_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_SalesOrderDetail_ProductIDNonclustered index.NoNoNONCLUSTEREDProductID
PK_SalesOrderDetail_SalesOrderID_SalesOrderDetailIDClustered index created by a primary key constraint.YesYesCLUSTEREDSalesOrderID, SalesOrderDetailID
+ + + + + + + + + +
+ Foreign Keys in SalesOrderDetail +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SalesOrderDetail_SalesOrderHeader_SalesOrderIDSalesOrderIDSales.SalesOrderHeader (SalesOrderID)Foreign key constraint referencing SalesOrderHeader.PurchaseOrderID.
FK_SalesOrderDetail_SpecialOfferProduct_SpecialOfferIDProductIDSpecialOfferID, ProductIDSales.SpecialOfferProduct (SpecialOfferID, ProductID)Foreign key constraint referencing SpecialOfferProduct.SpecialOfferIDProductID.

+ Sales.SalesOrderHeader (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ General sales order information. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesOrderIDint (identity)10,0NononeNoPrimary key.
RevisionNumbertinyint3,0No((0))NoIncremental number to track changes to the sales order over time.
OrderDatedatetimeNo(getdate())NoDates the sales order was created.
DueDatedatetimeNononeNoDate the order is due to the customer.
ShipDatedatetimeYesnoneNoDate the order was shipped to the customer.
Statustinyint3,0No((1))NoOrder current status. 1 = In process; 2 = Approved; 3 = Backordered; 4 = Rejected; 5 = Shipped; 6 = Cancelled
OnlineOrderFlagbitNo((1))No0 = Order placed by sales person. 1 = Order placed online by customer.
SalesOrderNumbernvarchar25NononeYesUnique sales order identification number.
PurchaseOrderNumbernvarchar25YesnoneNoCustomer purchase order number reference.
AccountNumbernvarchar15YesnoneNoFinancial accounting number reference.
CustomerIDint10,0NononeNoCustomer identification number. Foreign key to Customer.CustomerID.
ContactIDint10,0NononeNoCustomer contact identification number. Foreign key to Contact.ContactID.
SalesPersonIDint10,0YesnoneNoSales person who created the sales order. Foreign key to SalesPerson.SalePersonID.
TerritoryIDint10,0YesnoneNoTerritory in which the sale was made. Foreign key to SalesTerritory.SalesTerritoryID.
BillToAddressIDint10,0NononeNoCustomer billing address. Foreign key to Address.AddressID.
ShipToAddressIDint10,0NononeNoCustomer shipping address. Foreign key to Address.AddressID.
ShipMethodIDint10,0NononeNoShipping method. Foreign key to ShipMethod.ShipMethodID.
CreditCardIDint10,0YesnoneNoCredit card identification number. Foreign key to CreditCard.CreditCardID.
CreditCardApprovalCodevarchar15YesnoneNoApproval code provided by the credit card company.
CurrencyRateIDint10,0YesnoneNoCurrency exchange rate used. Foreign key to CurrencyRate.CurrencyRateID.
SubTotalmoney19,4No((0.00))NoSales subtotal. Computed as SUM(SalesOrderDetail.LineTotal)for the appropriate SalesOrderID.
TaxAmtmoney19,4No((0.00))NoTax amount.
Freightmoney19,4No((0.00))NoShipping cost.
TotalDuemoney19,4NononeYesTotal due from customer. Computed as Subtotal + TaxAmt + Freight.
Commentnvarchar128YesnoneNoSales representative comments.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + + + +
+ Indexes on SalesOrderHeader +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SalesOrderHeader_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
AK_SalesOrderHeader_SalesOrderNumberUnique nonclustered index.NoYesNONCLUSTEREDSalesOrderNumber
IX_SalesOrderHeader_CustomerIDNonclustered index.NoNoNONCLUSTEREDCustomerID
IX_SalesOrderHeader_SalesPersonIDNonclustered index.NoNoNONCLUSTEREDSalesPersonID
PK_SalesOrderHeader_SalesOrderIDClustered index created by a primary key constraint.YesYesCLUSTEREDSalesOrderID
+ + + + + + + + + + + + + + + + + + + + + + + +
+ Foreign Keys in SalesOrderHeader +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SalesOrderHeader_Address_BillToAddressIDBillToAddressIDPerson.Address (AddressID)Foreign key constraint referencing Address.AddressID.
FK_SalesOrderHeader_Address_ShipToAddressIDShipToAddressIDPerson.Address (AddressID)Foreign key constraint referencing Address.AddressID.
FK_SalesOrderHeader_Contact_ContactIDContactIDPerson.Contact (ContactID)Foreign key constraint referencing Contact.ContactID.
FK_SalesOrderHeader_CreditCard_CreditCardIDCreditCardIDSales.CreditCard (CreditCardID)Foreign key constraint referencing CreditCard.CreditCardID.
FK_SalesOrderHeader_CurrencyRate_CurrencyRateIDCurrencyRateIDSales.CurrencyRate (CurrencyRateID)Foreign key constraint referencing CurrencyRate.CurrencyRateID.
FK_SalesOrderHeader_Customer_CustomerIDCustomerIDSales.Customer (CustomerID)Foreign key constraint referencing Customer.CustomerID.
FK_SalesOrderHeader_SalesPerson_SalesPersonIDSalesPersonIDSales.SalesPerson (SalesPersonID)Foreign key constraint referencing SalesPerson.SalesPersonID.
FK_SalesOrderHeader_SalesTerritory_TerritoryIDTerritoryIDSales.SalesTerritory (TerritoryID)Foreign key constraint referencing SalesTerritory.TerritoryID.
FK_SalesOrderHeader_ShipMethod_ShipMethodIDShipMethodIDPurchasing.ShipMethod (ShipMethodID)Foreign key constraint referencing ShipMethod.ShipMethodID.

+ Sales.SalesOrderHeaderSalesReason (table) +

+ + + + + + + + + + + + + +
+ Cross-reference table mapping sales orders to sales reason codes. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesOrderIDint10,0NononeNoPrimary key. Foreign key to SalesOrderHeader.SalesOrderID.
SalesReasonIDint10,0NononeNoPrimary key. Foreign key to SalesReason.SalesReasonID.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on SalesOrderHeaderSalesReason +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_SalesOrderHeaderSalesReason_SalesOrderID_SalesReasonIDClustered index created by a primary key constraint.YesYesCLUSTEREDSalesOrderID, SalesReasonID
+ + + + + + + + + +
+ Foreign Keys in SalesOrderHeaderSalesReason +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SalesOrderHeaderSalesReason_SalesOrderHeader_SalesOrderIDSalesOrderIDSales.SalesOrderHeader (SalesOrderID)Foreign key constraint referencing SalesOrderHeader.SalesOrderID.
FK_SalesOrderHeaderSalesReason_SalesReason_SalesReasonIDSalesReasonIDSales.SalesReason (SalesReasonID)Foreign key constraint referencing SalesReason.SalesReasonID.

+ Sales.SalesPerson (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Sales representative current information. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesPersonIDint10,0NononeNoPrimary key for SalesPerson records.
TerritoryIDint10,0YesnoneNoTerritory currently assigned to. Foreign key to SalesTerritory.SalesTerritoryID.
SalesQuotamoney19,4YesnoneNoProjected yearly sales.
Bonusmoney19,4No((0.00))NoBonus due if quota is met.
CommissionPctsmallmoney10,4No((0.00))NoCommision percent received per sale.
SalesYTDmoney19,4No((0.00))NoSales total year to date.
SalesLastYearmoney19,4No((0.00))NoSales total of previous year.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on SalesPerson +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SalesPerson_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_SalesPerson_SalesPersonIDClustered index created by a primary key constraint.YesYesCLUSTEREDSalesPersonID
+ + + + + + + + + +
+ Foreign Keys in SalesPerson +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SalesPerson_Employee_SalesPersonIDSalesPersonIDHumanResources.Employee (EmployeeID)Foreign key constraint referencing Employee.EmployeeID.
FK_SalesPerson_SalesTerritory_TerritoryIDTerritoryIDSales.SalesTerritory (TerritoryID)Foreign key constraint referencing SalesTerritory.TerritoryID.

+ Sales.SalesPersonQuotaHistory (table) +

+ + + + + + + + + + + + + + + + + +
+ Sales performance tracking. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesPersonIDint10,0NononeNoSales person identification number. Foreign key to SalesPerson.SalesPersonID.
QuotaDatedatetimeNononeNoSales quota date.
SalesQuotamoney19,4NononeNoSales quota amount.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on SalesPersonQuotaHistory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SalesPersonQuotaHistory_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_SalesPersonQuotaHistory_SalesPersonID_QuotaDateClustered index created by a primary key constraint.YesYesCLUSTEREDSalesPersonID, QuotaDate
+ + + + + + + +
+ Foreign Keys in SalesPersonQuotaHistory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SalesPersonQuotaHistory_SalesPerson_SalesPersonIDSalesPersonIDSales.SalesPerson (SalesPersonID)Foreign key constraint referencing SalesPerson.SalesPersonID.

+ Sales.SalesReason (table) +

+ + + + + + + + + + + + + + + +
+ Lookup table of customer purchase reasons. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesReasonIDint (identity)10,0NononeNoPrimary key for SalesReason records.
Namenvarchar50NononeNoSales reason description.
ReasonTypenvarchar50NononeNoCategory the sales reason belongs to.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + +
+ Indexes on SalesReason +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
PK_SalesReason_SalesReasonIDClustered index created by a primary key constraint.YesYesCLUSTEREDSalesReasonID

+ Sales.SalesTaxRate (table) +

+ + + + + + + + + + + + + + + + + + + + + +
+ Tax rate lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesTaxRateIDint (identity)10,0NononeNoPrimary key for SalesTaxRate records.
StateProvinceIDint10,0NononeNoState, province, or country/region the sales tax applies to.
TaxTypetinyint3,0NononeNo1 = Tax applied to retail transactions, 2 = Tax applied to wholesale transactions, 3 = Tax applied to all sales (retail and wholesale) transactions.
TaxRatesmallmoney10,4No((0.00))NoTax rate amount.
Namenvarchar50NononeNoTax rate description.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on SalesTaxRate +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SalesTaxRate_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
AK_SalesTaxRate_StateProvinceID_TaxTypeUnique nonclustered index.NoYesNONCLUSTEREDStateProvinceID, TaxType
PK_SalesTaxRate_SalesTaxRateIDClustered index created by a primary key constraint.YesYesCLUSTEREDSalesTaxRateID
+ + + + + + + +
+ Foreign Keys in SalesTaxRate +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SalesTaxRate_StateProvince_StateProvinceIDStateProvinceIDPerson.StateProvince (StateProvinceID)Foreign key constraint referencing StateProvince.StateProvinceID.

+ Sales.SalesTerritory (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Sales territory lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
TerritoryIDint (identity)10,0NononeNoPrimary key for SalesTerritory records.
Namenvarchar50NononeNoSales territory description
CountryRegionCodenvarchar3NononeNoISO standard country or region code. Foreign key to CountryRegion.CountryRegionCode.
Groupnvarchar50NononeNoGeographic area to which the sales territory belong.
SalesYTDmoney19,4No((0.00))NoSales in the territory year to date.
SalesLastYearmoney19,4No((0.00))NoSales in the territory the previous year.
CostYTDmoney19,4No((0.00))NoBusiness costs in the territory year to date.
CostLastYearmoney19,4No((0.00))NoBusiness costs in the territory the previous year.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on SalesTerritory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SalesTerritory_NameUnique nonclustered index.NoYesNONCLUSTEREDName
AK_SalesTerritory_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_SalesTerritory_TerritoryIDClustered index created by a primary key constraint.YesYesCLUSTEREDTerritoryID

+ Sales.SalesTerritoryHistory (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Sales representative transfers to other sales territories. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SalesPersonIDint10,0NononeNoPrimary key for SalesTerritoryHistory records.
TerritoryIDint10,0NononeNoTerritory identification number. Foreign key to SalesTerritory.SalesTerritoryID.
StartDatedatetimeNononeNoDate the sales representive started work in the territory.
EndDatedatetimeYesnoneNoDate the sales representative left work in the territory.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on SalesTerritoryHistory +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SalesTerritoryHistory_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_SalesTerritoryHistory_SalesPersonID_StartDate_TerritoryIDClustered index created by a primary key constraint.YesYesCLUSTEREDSalesPersonID, StartDate, TerritoryID
+ + + + + + + + + +
+ Foreign Keys in SalesTerritoryHistory +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SalesTerritoryHistory_SalesPerson_SalesPersonIDSalesPersonIDSales.SalesPerson (SalesPersonID)Foreign key constraint referencing SalesPerson.SalesPersonID.
FK_SalesTerritoryHistory_SalesTerritory_TerritoryIDTerritoryIDSales.SalesTerritory (TerritoryID)Foreign key constraint referencing SalesTerritory.TerritoryID.

+ Sales.ShoppingCartItem (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Contains online customer orders until the order is submitted or cancelled. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
ShoppingCartItemIDint (identity)10,0NononeNoPrimary key for ShoppingCartItem records.
ShoppingCartIDnvarchar50NononeNoShopping cart identification number.
Quantityint10,0No((1))NoProduct quantity ordered.
ProductIDint10,0NononeNoProduct ordered. Foreign key to Product.ProductID.
DateCreateddatetimeNo(getdate())NoDate the time the record was created.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on ShoppingCartItem +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
IX_ShoppingCartItem_ShoppingCartID_ProductIDNonclustered index.NoNoNONCLUSTEREDShoppingCartID, ProductID
PK_ShoppingCartItem_ShoppingCartItemIDClustered index created by a primary key constraint.YesYesCLUSTEREDShoppingCartItemID
+ + + + + + + +
+ Foreign Keys in ShoppingCartItem +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_ShoppingCartItem_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.

+ Sales.SpecialOffer (table) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Sale discounts lookup table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SpecialOfferIDint (identity)10,0NononeNoPrimary key for SpecialOffer records.
Descriptionnvarchar255NononeNoDiscount description.
DiscountPctsmallmoney10,4No((0.00))NoDiscount precentage.
Typenvarchar50NononeNoDiscount type category.
Categorynvarchar50NononeNoGroup the discount applies to such as Reseller or Customer.
StartDatedatetimeNononeNoDiscount start date.
EndDatedatetimeNononeNoDiscount end date.
MinQtyint10,0No((0))NoMinimum discount percent allowed.
MaxQtyint10,0YesnoneNoMaximum discount percent allowed.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + +
+ Indexes on SpecialOffer +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SpecialOffer_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
PK_SpecialOffer_SpecialOfferIDClustered index created by a primary key constraint.YesYesCLUSTEREDSpecialOfferID

+ Sales.SpecialOfferProduct (table) +

+ + + + + + + + + + + + + + + +
+ Cross-reference table mapping products to special offer discounts. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
SpecialOfferIDint10,0NononeNoPrimary key for SpecialOfferProduct records.
ProductIDint10,0NononeNoProduct identification number. Foreign key to Product.ProductID.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + +
+ Indexes on SpecialOfferProduct +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_SpecialOfferProduct_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_SpecialOfferProduct_ProductIDNonclustered index.NoNoNONCLUSTEREDProductID
PK_SpecialOfferProduct_SpecialOfferID_ProductIDClustered index created by a primary key constraint.YesYesCLUSTEREDSpecialOfferID, ProductID
+ + + + + + + + + +
+ Foreign Keys in SpecialOfferProduct +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_SpecialOfferProduct_Product_ProductIDProductIDProduction.Product (ProductID)Foreign key constraint referencing Product.ProductID.
FK_SpecialOfferProduct_SpecialOffer_SpecialOfferIDSpecialOfferIDSales.SpecialOffer (SpecialOfferID)Foreign key constraint referencing SpecialOffer.SpecialOfferID.

+ Sales.Store (table) +

+ + + + + + + + + + + + + + + + + + + +
+ Customers (resellers) of Adventure Works products. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CustomerIDint10,0NononeNoPrimary key. Foreign key to Customer.CustomerID.
Namenvarchar50NononeNoName of the store.
SalesPersonIDint10,0YesnoneNoID of the sales person assigned to the customer. Foreign key to SalesPerson.SalesPersonID.
DemographicsxmlYesnoneNoDemographic informationg about the store such as the number of employees, annual sales and store type.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + +
+ Indexes on Store +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_Store_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_Store_SalesPersonIDNonclustered index.NoNoNONCLUSTEREDSalesPersonID
PK_Store_CustomerIDClustered index created by a primary key constraint.YesYesCLUSTEREDCustomerID
PXML_Store_DemographicsPrimary XML index.NoNoXMLDemographics
+ + + + + + + + + +
+ Foreign Keys in Store +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_Store_Customer_CustomerIDCustomerIDSales.Customer (CustomerID)Foreign key constraint referencing Customer.CustomerID.
FK_Store_SalesPerson_SalesPersonIDSalesPersonIDSales.SalesPerson (SalesPersonID)Foreign key constraint referencing SalesPerson.SalesPersonID

+ Sales.StoreContact (table) +

+ + + + + + + + + + + + + + + + + +
+ Cross-reference table mapping stores and their employees. +
Column NameSystem Data TypeSizeAllow NullDefaultIs ComputedDescription
CustomerIDint10,0NononeNoStore identification number. Foreign key to Customer.CustomerID.
ContactIDint10,0NononeNoContact (store employee) identification number. Foreign key to Contact.ContactID.
ContactTypeIDint10,0NononeNoContact type such as owner or purchasing agent. Foreign key to ContactType.ContactTypeID.
rowguiduniqueidentifierNo(newid())NoROWGUIDCOL number uniquely identifying the record. Used to support a merge replication sample.
ModifiedDatedatetimeNo(getdate())NoDate and time the record was last updated.
+ + + + + + + + + + + + + + + +
+ Indexes on StoreContact +
Index NameDescriptionIs Primary KeyIs UniqueIndex TypeColumns
AK_StoreContact_rowguidUnique nonclustered index. Used to support replication samples.NoYesNONCLUSTEREDrowguid
IX_StoreContact_ContactIDNonclustered index.NoNoNONCLUSTEREDContactID
IX_StoreContact_ContactTypeIDNonclustered index.NoNoNONCLUSTEREDContactTypeID
PK_StoreContact_CustomerID_ContactIDClustered index created by a primary key constraint.YesYesCLUSTEREDCustomerID, ContactID
+ + + + + + + + + + + +
+ Foreign Keys in StoreContact +
Foreign Key NameColumnsReferences Table (Columns)Description
FK_StoreContact_Contact_ContactIDContactIDPerson.Contact (ContactID)Foreign key constraint referencing Contact.ContactID.
FK_StoreContact_ContactType_ContactTypeIDContactTypeIDPerson.ContactType (ContactTypeID)Foreign key constraint referencing ContactType.ContactTypeID.
FK_StoreContact_Store_CustomerIDCustomerIDSales.Store (CustomerID)Foreign key constraint referencing Store.CustomerID.

+ Sales.vIndividualCustomer (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Individual customers (names and addresses) that purchase Adventure Works Cycles products online. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
CustomerIDint10,0NononeNo
Titlenvarchar8YesnoneNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
Suffixnvarchar10YesnoneNo
Phonenvarchar25YesnoneNo
EmailAddressnvarchar50YesnoneNo
EmailPromotionint10,0NononeNo
AddressTypenvarchar50NononeNo
AddressLine1nvarchar60NononeNo
AddressLine2nvarchar60YesnoneNo
Citynvarchar30NononeNo
StateProvinceNamenvarchar50NononeNo
PostalCodenvarchar15NononeNo
CountryRegionNamenvarchar50NononeNo
DemographicsxmlYesnoneNo

+ Sales.vIndividualDemographics (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Displays the content from each element in the xml column Demographics for each customer in the Sales.Individual table. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
CustomerIDint10,0NononeNo
TotalPurchaseYTDmoney19,4YesnoneNo
DateFirstPurchasedatetimeYesnoneNo
BirthDatedatetimeYesnoneNo
MaritalStatusnvarchar1YesnoneNo
YearlyIncomenvarchar30YesnoneNo
Gendernvarchar1YesnoneNo
TotalChildrenint10,0YesnoneNo
NumberChildrenAtHomeint10,0YesnoneNo
Educationnvarchar30YesnoneNo
Occupationnvarchar30YesnoneNo
HomeOwnerFlagbitYesnoneNo
NumberCarsOwnedint10,0YesnoneNo

+ Sales.vSalesPerson (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Sales representiatives (names and addresses) and their sales-related information. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
SalesPersonIDint10,0NononeNo
Titlenvarchar8YesnoneNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
Suffixnvarchar10YesnoneNo
JobTitlenvarchar50NononeNo
Phonenvarchar25YesnoneNo
EmailAddressnvarchar50YesnoneNo
EmailPromotionint10,0NononeNo
AddressLine1nvarchar60NononeNo
AddressLine2nvarchar60YesnoneNo
Citynvarchar30NononeNo
StateProvinceNamenvarchar50NononeNo
PostalCodenvarchar15NononeNo
CountryRegionNamenvarchar50NononeNo
TerritoryNamenvarchar50YesnoneNo
TerritoryGroupnvarchar50YesnoneNo
SalesQuotamoney19,4YesnoneNo
SalesYTDmoney19,4NononeNo
SalesLastYearmoney19,4NononeNo

+ Sales.vSalesPersonSalesByFiscalYears (view) +

+ + + + + + + + + + + + + + + + + + + + + +
+ Uses PIVOT to return aggregated sales information for each sales representative. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
SalesPersonIDint10,0YesnoneNo
FullNamenvarchar152YesnoneNo
Titlenvarchar50NononeNo
SalesTerritorynvarchar50NononeNo
2002money19,4YesnoneNo
2003money19,4YesnoneNo
2004money19,4YesnoneNo

+ Sales.vStoreWithDemographics (view) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Stores (names and addresses) that sell Adventure Works Cycles products to consumers. +
Column NameSystem Data TypeSizeAllow NullDefaultIs Computed
CustomerIDint10,0NononeNo
Namenvarchar50NononeNo
ContactTypenvarchar50NononeNo
Titlenvarchar8YesnoneNo
FirstNamenvarchar50NononeNo
MiddleNamenvarchar50YesnoneNo
LastNamenvarchar50NononeNo
Suffixnvarchar10YesnoneNo
Phonenvarchar25YesnoneNo
EmailAddressnvarchar50YesnoneNo
EmailPromotionint10,0NononeNo
AddressTypenvarchar50NononeNo
AddressLine1nvarchar60NononeNo
AddressLine2nvarchar60YesnoneNo
Citynvarchar30NononeNo
StateProvinceNamenvarchar50NononeNo
PostalCodenvarchar15NononeNo
CountryRegionNamenvarchar50NononeNo
AnnualSalesmoney19,4YesnoneNo
AnnualRevenuemoney19,4YesnoneNo
BankNamenvarchar50YesnoneNo
BusinessTypenvarchar5YesnoneNo
YearOpenedint10,0YesnoneNo
Specialtynvarchar50YesnoneNo
SquareFeetint10,0YesnoneNo
Brandsnvarchar30YesnoneNo
Internetnvarchar30YesnoneNo
NumberEmployeesint10,0YesnoneNo
+ + \ No newline at end of file diff --git a/DbDocGenerator/App.config b/DbDocGenerator/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/DbDocGenerator/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/DbDocGenerator/DbDocGenerator.csproj b/DbDocGenerator/DbDocGenerator.csproj new file mode 100644 index 0000000..1680ad8 --- /dev/null +++ b/DbDocGenerator/DbDocGenerator.csproj @@ -0,0 +1,16 @@ + + + + + net45 + Exe + DbDocGenerator + DbDocGenerator + false + + + + + + + \ No newline at end of file diff --git a/DbDocGenerator/Program.cs b/DbDocGenerator/Program.cs new file mode 100644 index 0000000..c51b39a --- /dev/null +++ b/DbDocGenerator/Program.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DbDocGenerator +{ + class Program + { + static void Main(string[] args) + { + } + } +} diff --git a/DbDocGenerator/Properties/AssemblyInfo.cs b/DbDocGenerator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..ccc3d33 --- /dev/null +++ b/DbDocGenerator/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DbDocGenerator")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DbDocGenerator")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fb062af3-78db-45d5-adb6-9a6e7a0da541")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DocumentationGeneratorApplication/App.config b/DocumentationGeneratorApplication/App.config new file mode 100644 index 0000000..a1efe9e --- /dev/null +++ b/DocumentationGeneratorApplication/App.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/DocumentationGeneratorApplication/DocumentationGeneratorApplication.csproj b/DocumentationGeneratorApplication/DocumentationGeneratorApplication.csproj new file mode 100644 index 0000000..7594656 --- /dev/null +++ b/DocumentationGeneratorApplication/DocumentationGeneratorApplication.csproj @@ -0,0 +1,21 @@ + + + + + net48 + true + WinExe + net.datacowboy.DocumentationGeneratorApplication + DocumentationGeneratorApplication + false + + + + + + + + + + + \ No newline at end of file diff --git a/DocumentationGeneratorApplication/FrmConnectionString.Designer.cs b/DocumentationGeneratorApplication/FrmConnectionString.Designer.cs new file mode 100644 index 0000000..79e6c9b --- /dev/null +++ b/DocumentationGeneratorApplication/FrmConnectionString.Designer.cs @@ -0,0 +1,251 @@ +namespace net.datacowboy.DocumentationGeneratorApplication +{ + partial class FrmConnectionString + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.lblServer = new System.Windows.Forms.Label(); + this.txtServer = new System.Windows.Forms.TextBox(); + this.lblDatabase = new System.Windows.Forms.Label(); + this.txtDatabase = new System.Windows.Forms.TextBox(); + this.gbAuthentication = new System.Windows.Forms.GroupBox(); + this.chkShowPassword = new System.Windows.Forms.CheckBox(); + this.txtPassword = new System.Windows.Forms.TextBox(); + this.txtUserName = new System.Windows.Forms.TextBox(); + this.lblPassword = new System.Windows.Forms.Label(); + this.lblUserName = new System.Windows.Forms.Label(); + this.rdoSql = new System.Windows.Forms.RadioButton(); + this.rdoIntegrated = new System.Windows.Forms.RadioButton(); + this.frmErrorProvider = new System.Windows.Forms.ErrorProvider(this.components); + this.btnTest = new System.Windows.Forms.Button(); + this.btnOk = new System.Windows.Forms.Button(); + this.btnCancel = new System.Windows.Forms.Button(); + this.gbAuthentication.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.frmErrorProvider)).BeginInit(); + this.SuspendLayout(); + // + // lblServer + // + this.lblServer.AutoSize = true; + this.lblServer.Location = new System.Drawing.Point(13, 13); + this.lblServer.Name = "lblServer"; + this.lblServer.Size = new System.Drawing.Size(107, 13); + this.lblServer.TabIndex = 0; + this.lblServer.Text = "Data Source (Server)"; + // + // txtServer + // + this.txtServer.Location = new System.Drawing.Point(143, 10); + this.txtServer.Name = "txtServer"; + this.txtServer.Size = new System.Drawing.Size(214, 20); + this.txtServer.TabIndex = 1; + this.txtServer.Validating += new System.ComponentModel.CancelEventHandler(this.txtServer_Validating); + // + // lblDatabase + // + this.lblDatabase.AutoSize = true; + this.lblDatabase.Location = new System.Drawing.Point(13, 46); + this.lblDatabase.Name = "lblDatabase"; + this.lblDatabase.Size = new System.Drawing.Size(125, 13); + this.lblDatabase.TabIndex = 2; + this.lblDatabase.Text = "Initial Catalog (Database)"; + // + // txtDatabase + // + this.txtDatabase.Location = new System.Drawing.Point(141, 46); + this.txtDatabase.Name = "txtDatabase"; + this.txtDatabase.Size = new System.Drawing.Size(214, 20); + this.txtDatabase.TabIndex = 3; + this.txtDatabase.Validating += new System.ComponentModel.CancelEventHandler(this.txtDatabase_Validating); + // + // gbAuthentication + // + this.gbAuthentication.Controls.Add(this.chkShowPassword); + this.gbAuthentication.Controls.Add(this.txtPassword); + this.gbAuthentication.Controls.Add(this.txtUserName); + this.gbAuthentication.Controls.Add(this.lblPassword); + this.gbAuthentication.Controls.Add(this.lblUserName); + this.gbAuthentication.Controls.Add(this.rdoSql); + this.gbAuthentication.Controls.Add(this.rdoIntegrated); + this.gbAuthentication.Location = new System.Drawing.Point(16, 73); + this.gbAuthentication.Name = "gbAuthentication"; + this.gbAuthentication.Size = new System.Drawing.Size(288, 141); + this.gbAuthentication.TabIndex = 4; + this.gbAuthentication.TabStop = false; + this.gbAuthentication.Text = "Authentication"; + // + // chkShowPassword + // + this.chkShowPassword.AutoSize = true; + this.chkShowPassword.Location = new System.Drawing.Point(22, 118); + this.chkShowPassword.Name = "chkShowPassword"; + this.chkShowPassword.Size = new System.Drawing.Size(101, 17); + this.chkShowPassword.TabIndex = 6; + this.chkShowPassword.Text = "Show password"; + this.chkShowPassword.UseVisualStyleBackColor = true; + this.chkShowPassword.CheckedChanged += new System.EventHandler(this.chkShowPassword_CheckedChanged); + // + // txtPassword + // + this.txtPassword.Location = new System.Drawing.Point(76, 93); + this.txtPassword.Name = "txtPassword"; + this.txtPassword.PasswordChar = '*'; + this.txtPassword.Size = new System.Drawing.Size(192, 20); + this.txtPassword.TabIndex = 5; + // + // txtUserName + // + this.txtUserName.Location = new System.Drawing.Point(74, 66); + this.txtUserName.Name = "txtUserName"; + this.txtUserName.Size = new System.Drawing.Size(194, 20); + this.txtUserName.TabIndex = 4; + this.txtUserName.Validating += new System.ComponentModel.CancelEventHandler(this.txtUserName_Validating); + // + // lblPassword + // + this.lblPassword.AutoSize = true; + this.lblPassword.Location = new System.Drawing.Point(10, 95); + this.lblPassword.Name = "lblPassword"; + this.lblPassword.Size = new System.Drawing.Size(53, 13); + this.lblPassword.TabIndex = 3; + this.lblPassword.Text = "Password"; + // + // lblUserName + // + this.lblUserName.AutoSize = true; + this.lblUserName.Location = new System.Drawing.Point(7, 68); + this.lblUserName.Name = "lblUserName"; + this.lblUserName.Size = new System.Drawing.Size(58, 13); + this.lblUserName.TabIndex = 2; + this.lblUserName.Text = "User name"; + // + // rdoSql + // + this.rdoSql.AutoSize = true; + this.rdoSql.Location = new System.Drawing.Point(7, 44); + this.rdoSql.Name = "rdoSql"; + this.rdoSql.Size = new System.Drawing.Size(46, 17); + this.rdoSql.TabIndex = 1; + this.rdoSql.TabStop = true; + this.rdoSql.Text = "SQL"; + this.rdoSql.UseVisualStyleBackColor = true; + this.rdoSql.CheckedChanged += new System.EventHandler(this.rdoSql_CheckedChanged); + // + // rdoIntegrated + // + this.rdoIntegrated.AutoSize = true; + this.rdoIntegrated.Location = new System.Drawing.Point(7, 20); + this.rdoIntegrated.Name = "rdoIntegrated"; + this.rdoIntegrated.Size = new System.Drawing.Size(114, 17); + this.rdoIntegrated.TabIndex = 0; + this.rdoIntegrated.TabStop = true; + this.rdoIntegrated.Text = "Integrated Security"; + this.rdoIntegrated.UseVisualStyleBackColor = true; + this.rdoIntegrated.CheckedChanged += new System.EventHandler(this.rdoIntegrated_CheckedChanged); + // + // frmErrorProvider + // + this.frmErrorProvider.ContainerControl = this; + // + // btnTest + // + this.btnTest.Location = new System.Drawing.Point(16, 220); + this.btnTest.Name = "btnTest"; + this.btnTest.Size = new System.Drawing.Size(111, 23); + this.btnTest.TabIndex = 5; + this.btnTest.Text = "Test Connection"; + this.btnTest.UseVisualStyleBackColor = true; + this.btnTest.Click += new System.EventHandler(this.btnTest_Click); + // + // btnOk + // + this.btnOk.Location = new System.Drawing.Point(179, 257); + this.btnOk.Name = "btnOk"; + this.btnOk.Size = new System.Drawing.Size(75, 23); + this.btnOk.TabIndex = 6; + this.btnOk.Text = "OK"; + this.btnOk.UseVisualStyleBackColor = true; + this.btnOk.Click += new System.EventHandler(this.btnOk_Click); + // + // btnCancel + // + this.btnCancel.CausesValidation = false; + this.btnCancel.Location = new System.Drawing.Point(258, 257); + this.btnCancel.Name = "btnCancel"; + this.btnCancel.Size = new System.Drawing.Size(75, 23); + this.btnCancel.TabIndex = 7; + this.btnCancel.Text = "Cancel"; + this.btnCancel.UseVisualStyleBackColor = true; + this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); + // + // FrmConnectionString + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(391, 292); + this.Controls.Add(this.btnCancel); + this.Controls.Add(this.btnOk); + this.Controls.Add(this.btnTest); + this.Controls.Add(this.gbAuthentication); + this.Controls.Add(this.txtDatabase); + this.Controls.Add(this.lblDatabase); + this.Controls.Add(this.txtServer); + this.Controls.Add(this.lblServer); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "FrmConnectionString"; + this.Text = "Connection String"; + this.Load += new System.EventHandler(this.FrmConnectionString_Load); + this.gbAuthentication.ResumeLayout(false); + this.gbAuthentication.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.frmErrorProvider)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lblServer; + private System.Windows.Forms.TextBox txtServer; + private System.Windows.Forms.Label lblDatabase; + private System.Windows.Forms.TextBox txtDatabase; + private System.Windows.Forms.GroupBox gbAuthentication; + private System.Windows.Forms.RadioButton rdoIntegrated; + private System.Windows.Forms.Label lblUserName; + private System.Windows.Forms.RadioButton rdoSql; + private System.Windows.Forms.Label lblPassword; + private System.Windows.Forms.TextBox txtPassword; + private System.Windows.Forms.TextBox txtUserName; + private System.Windows.Forms.CheckBox chkShowPassword; + private System.Windows.Forms.ErrorProvider frmErrorProvider; + private System.Windows.Forms.Button btnTest; + private System.Windows.Forms.Button btnOk; + private System.Windows.Forms.Button btnCancel; + } +} \ No newline at end of file diff --git a/DocumentationGeneratorApplication/FrmConnectionString.cs b/DocumentationGeneratorApplication/FrmConnectionString.cs new file mode 100644 index 0000000..383ccfb --- /dev/null +++ b/DocumentationGeneratorApplication/FrmConnectionString.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using System.Data.SqlClient; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace net.datacowboy.DocumentationGeneratorApplication +{ + public partial class FrmConnectionString : Form + { + + + /// + /// Dialog result for form + /// + public DialogResult Result = DialogResult.Cancel; + + /// + /// Create SQL server connection string based on inputs in UI + /// + /// + public string GetConnectionString() + { + + try + { + var builder = new SqlConnectionStringBuilder(); + + if (!String.IsNullOrEmpty(this.txtServer.Text)) + { + builder.DataSource = this.txtServer.Text.Trim(); + } + + if (!String.IsNullOrWhiteSpace(this.txtDatabase.Text)) + { + builder.InitialCatalog = this.txtDatabase.Text.Trim(); + } + + //assume integrated security unless SQL checked + builder.IntegratedSecurity = !this.rdoSql.Checked; + if (this.rdoSql.Checked) + { + if (!String.IsNullOrWhiteSpace(this.txtUserName.Text)) + { + builder.UserID = this.txtUserName.Text; + } + + //allow empty password, don't check for length + builder.Password = this.txtPassword.Text ?? String.Empty; + } + + return builder.ToString(); + } + catch (Exception ex) + { + MessageBox.Show(String.Format("Unable to create connection string:\n{0}", ex.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + return String.Empty; + } + } + + //Inetgrated secuirty mode selected + private void integratedSecurityModeSetup() + { + this.txtUserName.Clear(); + this.txtPassword.Clear(); + this.txtUserName.Enabled = false; + this.txtPassword.Enabled = false; + this.chkShowPassword.Enabled = false; + this.frmErrorProvider.SetError(this.rdoIntegrated, String.Empty); + } + + //SQL auth security mode selected + private void sqlAuthModeSetup() + { + this.txtUserName.Clear(); + this.txtPassword.Clear(); + this.txtUserName.Enabled = true; + this.txtPassword.Enabled = true; + this.chkShowPassword.Enabled = true; + this.frmErrorProvider.SetError(this.rdoIntegrated, String.Empty); + } + + + + public FrmConnectionString(string connectionString="") + { + InitializeComponent(); + + SqlConnectionStringBuilder builder = null; + + if (!String.IsNullOrWhiteSpace(connectionString)) + { + + try + { + + + builder = new SqlConnectionStringBuilder(connectionString); + } + catch (Exception ex) + { + //TODO: tell user something wrong in initial connection string + MessageBox.Show(String.Format("Unable to parse initial connection string:\n{0}", ex.Message), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + builder = new SqlConnectionStringBuilder(); + + } + + if (!String.IsNullOrWhiteSpace(builder.DataSource)) + { + this.txtServer.Text = builder.DataSource; + } + + if (!String.IsNullOrWhiteSpace(builder.InitialCatalog)) + { + this.txtDatabase.Text = builder.InitialCatalog; + } + + if (builder.IntegratedSecurity) + { + this.rdoIntegrated.Checked = true; + this.integratedSecurityModeSetup(); + } + else + { + this.rdoSql.Checked = true; + this.sqlAuthModeSetup(); + + if (!String.IsNullOrWhiteSpace(builder.UserID)) + { + this.txtUserName.Text = builder.UserID; + } + + if (!String.IsNullOrWhiteSpace(builder.Password)) + { + this.txtPassword.Text = builder.Password; + } + } + + + + } + + } + + private void FrmConnectionString_Load(object sender, EventArgs e) + { + + } + + private void rdoIntegrated_CheckedChanged(object sender, EventArgs e) + { + if (this.rdoIntegrated.Checked) + { + this.integratedSecurityModeSetup(); + } + } + + private void rdoSql_CheckedChanged(object sender, EventArgs e) + { + if (this.rdoSql.Checked) + { + this.sqlAuthModeSetup(); + } + } + + //control if password characters shown or hidden + private void chkShowPassword_CheckedChanged(object sender, EventArgs e) + { + if (this.chkShowPassword.Checked) + { + this.txtPassword.PasswordChar = '\0'; + } + else + { + this.txtPassword.PasswordChar = '*'; + } + } + + private void btnTest_Click(object sender, EventArgs e) + { + this.enableOrDisableUi(false); + + if (this.validateUiInput()) + { + this.testConnectionString(); + } + + this.enableOrDisableUi(true); + } + + /// + /// Enable or disable UI controls + /// + /// True to enable, false to disable + private void enableOrDisableUi(bool enable) + { + this.Enabled = enable; + } + + + private void testConnectionString() + { + string connStr = this.GetConnectionString(); + + + var testResult = SqlConnectionTester.TestConnectionString(connStr); + + if (testResult.Success) + { + MessageBox.Show("Connection test successful", "Connection Test Result", MessageBoxButtons.OK, MessageBoxIcon.None); + } + else + { + MessageBox.Show(String.Format("Connection test failed.\n{0}", testResult.ErrorMessage), "Connection Test Result", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + + private void btnOk_Click(object sender, EventArgs e) + { + //if UI valid close form + if (this.validateUiInput()) + { + this.Result = System.Windows.Forms.DialogResult.OK; + this.Close(); + } + } + + private void btnCancel_Click(object sender, EventArgs e) + { + this.Result = System.Windows.Forms.DialogResult.Cancel; + this.Close(); + } + + private bool validateUiInput() + { + this.frmErrorProvider.Clear(); + + bool errorFound = false; + + if (String.IsNullOrWhiteSpace(this.txtServer.Text)) + { + errorFound = true; + this.frmErrorProvider.SetError(this.txtServer, "Server cannot be empty"); + } + + if (String.IsNullOrWhiteSpace(this.txtDatabase.Text)) + { + errorFound = true; + this.frmErrorProvider.SetError(this.txtDatabase, "Database cannot be empty"); + } + + if (this.rdoSql.Checked) + { + if(String.IsNullOrWhiteSpace(this.txtUserName.Text)) + { + errorFound = true; + this.frmErrorProvider.SetError(this.txtUserName, "User Name cannot be empty"); + } + + //allow empty password + } + else + { + //either Integrated or SQL should be checked + if (!this.rdoIntegrated.Checked) + { + errorFound = true; + this.frmErrorProvider.SetError(this.rdoIntegrated, "Please check an authentication type"); + + } + } + + return !errorFound; + + } + + private void txtUserName_Validating(object sender, CancelEventArgs e) + { + if (!String.IsNullOrWhiteSpace(this.txtUserName.Text)) + { + this.frmErrorProvider.SetError(this.txtUserName, String.Empty); + } + } + + + private void txtServer_Validating(object sender, CancelEventArgs e) + { + if (!String.IsNullOrWhiteSpace(this.txtServer.Text)) + { + this.frmErrorProvider.SetError(this.txtServer, String.Empty); + } + } + + private void txtDatabase_Validating(object sender, CancelEventArgs e) + { + if (!String.IsNullOrWhiteSpace(this.txtDatabase.Text)) + { + this.frmErrorProvider.SetError(this.txtDatabase, String.Empty); + } + } + } +} diff --git a/DocumentationGeneratorApplication/FrmConnectionString.resx b/DocumentationGeneratorApplication/FrmConnectionString.resx new file mode 100644 index 0000000..b46ed58 --- /dev/null +++ b/DocumentationGeneratorApplication/FrmConnectionString.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/DocumentationGeneratorApplication/FrmObjectsWithoutDescription.Designer.cs b/DocumentationGeneratorApplication/FrmObjectsWithoutDescription.Designer.cs new file mode 100644 index 0000000..58de129 --- /dev/null +++ b/DocumentationGeneratorApplication/FrmObjectsWithoutDescription.Designer.cs @@ -0,0 +1,149 @@ +namespace net.datacowboy.DocumentationGeneratorApplication +{ + partial class FrmObjectsWithoutDescription + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.gvObjects = new System.Windows.Forms.DataGridView(); + this.btnShowSqlScript = new System.Windows.Forms.Button(); + this.lblFormDescription = new System.Windows.Forms.Label(); + this.txtSqlScript = new System.Windows.Forms.TextBox(); + this.lnkCopyToClipboard = new System.Windows.Forms.LinkLabel(); + this.ObjectName = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.ObjectType = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.Description = new System.Windows.Forms.DataGridViewTextBoxColumn(); + ((System.ComponentModel.ISupportInitialize)(this.gvObjects)).BeginInit(); + this.SuspendLayout(); + // + // gvObjects + // + this.gvObjects.AllowUserToAddRows = false; + this.gvObjects.AllowUserToDeleteRows = false; + this.gvObjects.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.gvObjects.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.ObjectName, + this.ObjectType, + this.Description}); + this.gvObjects.Location = new System.Drawing.Point(12, 35); + this.gvObjects.Name = "gvObjects"; + this.gvObjects.Size = new System.Drawing.Size(793, 150); + this.gvObjects.TabIndex = 0; + // + // btnShowSqlScript + // + this.btnShowSqlScript.Location = new System.Drawing.Point(13, 211); + this.btnShowSqlScript.Name = "btnShowSqlScript"; + this.btnShowSqlScript.Size = new System.Drawing.Size(148, 23); + this.btnShowSqlScript.TabIndex = 1; + this.btnShowSqlScript.Text = "Show SQL Scripts"; + this.btnShowSqlScript.UseVisualStyleBackColor = true; + this.btnShowSqlScript.Click += new System.EventHandler(this.btnShowSqlScript_Click); + // + // lblFormDescription + // + this.lblFormDescription.AutoSize = true; + this.lblFormDescription.Location = new System.Drawing.Point(13, 13); + this.lblFormDescription.Name = "lblFormDescription"; + this.lblFormDescription.Size = new System.Drawing.Size(675, 13); + this.lblFormDescription.TabIndex = 2; + this.lblFormDescription.Text = "To generate the SQL commands to add descriptions to database objects fill in the " + + "Description column then click the Show SQL Scripts button."; + // + // txtSqlScript + // + this.txtSqlScript.Location = new System.Drawing.Point(12, 259); + this.txtSqlScript.Multiline = true; + this.txtSqlScript.Name = "txtSqlScript"; + this.txtSqlScript.ReadOnly = true; + this.txtSqlScript.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtSqlScript.Size = new System.Drawing.Size(884, 90); + this.txtSqlScript.TabIndex = 3; + // + // lnkCopyToClipboard + // + this.lnkCopyToClipboard.AutoSize = true; + this.lnkCopyToClipboard.Location = new System.Drawing.Point(16, 356); + this.lnkCopyToClipboard.Name = "lnkCopyToClipboard"; + this.lnkCopyToClipboard.Size = new System.Drawing.Size(90, 13); + this.lnkCopyToClipboard.TabIndex = 4; + this.lnkCopyToClipboard.TabStop = true; + this.lnkCopyToClipboard.Text = "Copy to Clipboard"; + this.lnkCopyToClipboard.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.lnkCopyToClipboard_LinkClicked); + // + // ObjectName + // + this.ObjectName.DataPropertyName = "ObjectFullDisplayName"; + this.ObjectName.HeaderText = "Object Name"; + this.ObjectName.Name = "ObjectName"; + this.ObjectName.ReadOnly = true; + this.ObjectName.Width = 150; + // + // ObjectType + // + this.ObjectType.DataPropertyName = "ObjectTypeDisplayText"; + this.ObjectType.HeaderText = "Object Type"; + this.ObjectType.Name = "ObjectType"; + this.ObjectType.ReadOnly = true; + // + // Description + // + this.Description.DataPropertyName = "Description"; + this.Description.HeaderText = "Description"; + this.Description.Name = "Description"; + this.Description.Width = 500; + // + // FrmObjectsWithoutDescription + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(908, 378); + this.Controls.Add(this.lnkCopyToClipboard); + this.Controls.Add(this.txtSqlScript); + this.Controls.Add(this.lblFormDescription); + this.Controls.Add(this.btnShowSqlScript); + this.Controls.Add(this.gvObjects); + this.Name = "FrmObjectsWithoutDescription"; + this.Text = "Objects Without a Description"; + this.Load += new System.EventHandler(this.FrmObjectsWithoutDescription_Load); + ((System.ComponentModel.ISupportInitialize)(this.gvObjects)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.DataGridView gvObjects; + private System.Windows.Forms.Button btnShowSqlScript; + private System.Windows.Forms.Label lblFormDescription; + private System.Windows.Forms.TextBox txtSqlScript; + private System.Windows.Forms.LinkLabel lnkCopyToClipboard; + private System.Windows.Forms.DataGridViewTextBoxColumn ObjectName; + private System.Windows.Forms.DataGridViewTextBoxColumn ObjectType; + private System.Windows.Forms.DataGridViewTextBoxColumn Description; + } +} \ No newline at end of file diff --git a/DocumentationGeneratorApplication/FrmObjectsWithoutDescription.cs b/DocumentationGeneratorApplication/FrmObjectsWithoutDescription.cs new file mode 100644 index 0000000..d7fd6b5 --- /dev/null +++ b/DocumentationGeneratorApplication/FrmObjectsWithoutDescription.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Diagnostics; +using System.Text; +using System.Windows.Forms; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; +using Be.Timvw.Framework.ComponentModel; +using Be.Timvw.Framework.Collections.Generic; + +namespace net.datacowboy.DocumentationGeneratorApplication +{ + public partial class FrmObjectsWithoutDescription : Form + { + private net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model.Database database; + + private SortableBindingList boundList; + + public FrmObjectsWithoutDescription(net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model.Database database) + { + InitializeComponent(); + + this.database = database; + } + + private void FrmObjectsWithoutDescription_Load(object sender, EventArgs e) + { + this.boundList = new SortableBindingList(this.database.FindObjectsWithoutDescriptionInDatabase()); + + + this.gvObjects.AutoGenerateColumns = false; + this.gvObjects.DataSource = this.boundList; + + this.lnkCopyToClipboard.Visible = false; + } + + private void btnShowSqlScript_Click(object sender, EventArgs e) + { + this.txtSqlScript.Clear(); + this.lnkCopyToClipboard.Visible = false; + + + if (this.boundList.HasAny()) + { + //filter for edits + var updatedList = this.boundList.Where(i => !String.IsNullOrWhiteSpace(i.Description)).ToArray(); + + + if (updatedList.HasAny()) + { + var sb = new StringBuilder(); + + sb.Append("USE ["); + sb.Append(this.database.DatabaseName); + sb.Append("];"); + + + + for(int i=0; i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + True + + + True + + + True + + \ No newline at end of file diff --git a/DocumentationGeneratorApplication/MainForm.Designer.cs b/DocumentationGeneratorApplication/MainForm.Designer.cs new file mode 100644 index 0000000..c0ae13a --- /dev/null +++ b/DocumentationGeneratorApplication/MainForm.Designer.cs @@ -0,0 +1,236 @@ +namespace net.datacowboy.DocumentationGeneratorApplication +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + this.lblConnectionString = new System.Windows.Forms.Label(); + this.txtConnectionString = new System.Windows.Forms.TextBox(); + this.lblDocFile = new System.Windows.Forms.Label(); + this.txtDocFile = new System.Windows.Forms.TextBox(); + this.btnGenerateDoc = new System.Windows.Forms.Button(); + this.chkOpenDoc = new System.Windows.Forms.CheckBox(); + this.errorProviderMainForm = new System.Windows.Forms.ErrorProvider(this.components); + this.btnDocFileBrowse = new System.Windows.Forms.Button(); + this.btnEditConnection = new System.Windows.Forms.Button(); + this.btnFindObjectsWithoutDescription = new System.Windows.Forms.Button(); + this.ttMain = new System.Windows.Forms.ToolTip(this.components); + this.chkCheckForDatabaseDesignIssues = new System.Windows.Forms.CheckBox(); + this.chkFkToTableHyperLink = new System.Windows.Forms.CheckBox(); + this.statusStripMain = new System.Windows.Forms.StatusStrip(); + this.toolStripProgressBar = new System.Windows.Forms.ToolStripProgressBar(); + this.toolStripStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + ((System.ComponentModel.ISupportInitialize)(this.errorProviderMainForm)).BeginInit(); + this.statusStripMain.SuspendLayout(); + this.SuspendLayout(); + // + // lblConnectionString + // + this.lblConnectionString.AutoSize = true; + this.lblConnectionString.Location = new System.Drawing.Point(13, 13); + this.lblConnectionString.Name = "lblConnectionString"; + this.lblConnectionString.Size = new System.Drawing.Size(91, 13); + this.lblConnectionString.TabIndex = 0; + this.lblConnectionString.Text = "Connection String"; + // + // txtConnectionString + // + this.txtConnectionString.Location = new System.Drawing.Point(12, 29); + this.txtConnectionString.Name = "txtConnectionString"; + this.txtConnectionString.Size = new System.Drawing.Size(471, 20); + this.txtConnectionString.TabIndex = 1; + // + // lblDocFile + // + this.lblDocFile.AutoSize = true; + this.lblDocFile.Location = new System.Drawing.Point(12, 70); + this.lblDocFile.Name = "lblDocFile"; + this.lblDocFile.Size = new System.Drawing.Size(98, 13); + this.lblDocFile.TabIndex = 2; + this.lblDocFile.Text = "Documentation File"; + // + // txtDocFile + // + this.txtDocFile.Location = new System.Drawing.Point(15, 87); + this.txtDocFile.Name = "txtDocFile"; + this.txtDocFile.Size = new System.Drawing.Size(387, 20); + this.txtDocFile.TabIndex = 3; + // + // btnGenerateDoc + // + this.btnGenerateDoc.Location = new System.Drawing.Point(12, 209); + this.btnGenerateDoc.Name = "btnGenerateDoc"; + this.btnGenerateDoc.Size = new System.Drawing.Size(150, 23); + this.btnGenerateDoc.TabIndex = 8; + this.btnGenerateDoc.Text = "Generate Documentation"; + this.ttMain.SetToolTip(this.btnGenerateDoc, "Generate documentation file"); + this.btnGenerateDoc.UseVisualStyleBackColor = true; + this.btnGenerateDoc.Click += new System.EventHandler(this.btnGenerateDoc_Click); + // + // chkOpenDoc + // + this.chkOpenDoc.AutoSize = true; + this.chkOpenDoc.Checked = true; + this.chkOpenDoc.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkOpenDoc.Location = new System.Drawing.Point(16, 114); + this.chkOpenDoc.Name = "chkOpenDoc"; + this.chkOpenDoc.Size = new System.Drawing.Size(178, 17); + this.chkOpenDoc.TabIndex = 5; + this.chkOpenDoc.Text = "Open Document When Finished"; + this.chkOpenDoc.UseVisualStyleBackColor = true; + // + // errorProviderMainForm + // + this.errorProviderMainForm.ContainerControl = this; + // + // btnDocFileBrowse + // + this.btnDocFileBrowse.Location = new System.Drawing.Point(421, 87); + this.btnDocFileBrowse.Name = "btnDocFileBrowse"; + this.btnDocFileBrowse.Size = new System.Drawing.Size(75, 23); + this.btnDocFileBrowse.TabIndex = 4; + this.btnDocFileBrowse.Text = "Browse..."; + this.ttMain.SetToolTip(this.btnDocFileBrowse, "Browse for documentation file location"); + this.btnDocFileBrowse.UseVisualStyleBackColor = true; + this.btnDocFileBrowse.Click += new System.EventHandler(this.btnDocFileBrowse_Click); + // + // btnEditConnection + // + this.btnEditConnection.Location = new System.Drawing.Point(497, 26); + this.btnEditConnection.Name = "btnEditConnection"; + this.btnEditConnection.Size = new System.Drawing.Size(75, 23); + this.btnEditConnection.TabIndex = 2; + this.btnEditConnection.Text = "Edit..."; + this.ttMain.SetToolTip(this.btnEditConnection, "Edit connection string"); + this.btnEditConnection.UseVisualStyleBackColor = true; + this.btnEditConnection.Click += new System.EventHandler(this.btnEditConnection_Click); + // + // btnFindObjectsWithoutDescription + // + this.btnFindObjectsWithoutDescription.Location = new System.Drawing.Point(187, 209); + this.btnFindObjectsWithoutDescription.Name = "btnFindObjectsWithoutDescription"; + this.btnFindObjectsWithoutDescription.Size = new System.Drawing.Size(192, 23); + this.btnFindObjectsWithoutDescription.TabIndex = 9; + this.btnFindObjectsWithoutDescription.Text = "Find Objects Without a Description"; + this.ttMain.SetToolTip(this.btnFindObjectsWithoutDescription, "Find database objects without a description"); + this.btnFindObjectsWithoutDescription.UseVisualStyleBackColor = true; + this.btnFindObjectsWithoutDescription.Click += new System.EventHandler(this.btnFindObjectsWithoutDescription_Click); + // + // chkCheckForDatabaseDesignIssues + // + this.chkCheckForDatabaseDesignIssues.AutoSize = true; + this.chkCheckForDatabaseDesignIssues.Location = new System.Drawing.Point(16, 162); + this.chkCheckForDatabaseDesignIssues.Name = "chkCheckForDatabaseDesignIssues"; + this.chkCheckForDatabaseDesignIssues.Size = new System.Drawing.Size(190, 17); + this.chkCheckForDatabaseDesignIssues.TabIndex = 7; + this.chkCheckForDatabaseDesignIssues.Text = "Check for Database Design Issues"; + this.ttMain.SetToolTip(this.chkCheckForDatabaseDesignIssues, "Check for possible database design issues"); + this.chkCheckForDatabaseDesignIssues.UseVisualStyleBackColor = true; + // + // chkFkToTableHyperLink + // + this.chkFkToTableHyperLink.AutoSize = true; + this.chkFkToTableHyperLink.Checked = true; + this.chkFkToTableHyperLink.CheckState = System.Windows.Forms.CheckState.Checked; + this.chkFkToTableHyperLink.Location = new System.Drawing.Point(15, 138); + this.chkFkToTableHyperLink.Name = "chkFkToTableHyperLink"; + this.chkFkToTableHyperLink.Size = new System.Drawing.Size(286, 17); + this.chkFkToTableHyperLink.TabIndex = 6; + this.chkFkToTableHyperLink.Text = "Generate Foreign Key to Table Hyperlinks in Document"; + this.chkFkToTableHyperLink.UseVisualStyleBackColor = true; + // + // statusStripMain + // + this.statusStripMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.toolStripProgressBar, + this.toolStripStatusLabel}); + this.statusStripMain.Location = new System.Drawing.Point(0, 301); + this.statusStripMain.Name = "statusStripMain"; + this.statusStripMain.Size = new System.Drawing.Size(635, 22); + this.statusStripMain.TabIndex = 11; + this.statusStripMain.Text = "fd"; + // + // toolStripProgressBar + // + this.toolStripProgressBar.Name = "toolStripProgressBar"; + this.toolStripProgressBar.Size = new System.Drawing.Size(100, 16); + // + // toolStripStatusLabel + // + this.toolStripStatusLabel.Name = "toolStripStatusLabel"; + this.toolStripStatusLabel.Size = new System.Drawing.Size(0, 17); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(635, 323); + this.Controls.Add(this.statusStripMain); + this.Controls.Add(this.chkCheckForDatabaseDesignIssues); + this.Controls.Add(this.chkFkToTableHyperLink); + this.Controls.Add(this.btnFindObjectsWithoutDescription); + this.Controls.Add(this.btnEditConnection); + this.Controls.Add(this.btnDocFileBrowse); + this.Controls.Add(this.chkOpenDoc); + this.Controls.Add(this.btnGenerateDoc); + this.Controls.Add(this.txtDocFile); + this.Controls.Add(this.lblDocFile); + this.Controls.Add(this.txtConnectionString); + this.Controls.Add(this.lblConnectionString); + this.Name = "MainForm"; + this.Text = "SQL Server Database Documentation"; + this.Load += new System.EventHandler(this.MainForm_Load); + ((System.ComponentModel.ISupportInitialize)(this.errorProviderMainForm)).EndInit(); + this.statusStripMain.ResumeLayout(false); + this.statusStripMain.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label lblConnectionString; + private System.Windows.Forms.TextBox txtConnectionString; + private System.Windows.Forms.Label lblDocFile; + private System.Windows.Forms.TextBox txtDocFile; + private System.Windows.Forms.Button btnGenerateDoc; + private System.Windows.Forms.CheckBox chkOpenDoc; + private System.Windows.Forms.ErrorProvider errorProviderMainForm; + private System.Windows.Forms.Button btnDocFileBrowse; + private System.Windows.Forms.Button btnEditConnection; + private System.Windows.Forms.Button btnFindObjectsWithoutDescription; + private System.Windows.Forms.ToolTip ttMain; + private System.Windows.Forms.CheckBox chkFkToTableHyperLink; + private System.Windows.Forms.CheckBox chkCheckForDatabaseDesignIssues; + private System.Windows.Forms.StatusStrip statusStripMain; + private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar; + private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel; + } +} + diff --git a/DocumentationGeneratorApplication/MainForm.cs b/DocumentationGeneratorApplication/MainForm.cs new file mode 100644 index 0000000..bba9afb --- /dev/null +++ b/DocumentationGeneratorApplication/MainForm.cs @@ -0,0 +1,330 @@ +using System; +using System.Linq; +using System.Windows.Forms; +using System.IO; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using System.Configuration; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Document; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.DocumentationGeneratorApplication +{ + public partial class MainForm : Form + { + + public MainForm() + { + InitializeComponent(); + } + + private void MainForm_Load(object sender, EventArgs e) + { + //provide defaults + + this.txtConnectionString.Text = this.getConnectionStringFromAppConfig() ?? String.Empty; + + this.txtDocFile.Text = "documentation.html"; + + + } + + /// + /// Get database connection string from app.config file + /// Connection string named "default" in app.config + /// + /// + private string getConnectionStringFromAppConfig() + { + string connString = null; + + + try + { + // Try to access ConfigurationManager via reflection so the code compiles + // even when System.Configuration assembly is not referenced (e.g. .NET Core). + var cmType = Type.GetType("System.Configuration.ConfigurationManager, System.Configuration.ConfigurationManager") + ?? Type.GetType("System.Configuration.ConfigurationManager"); + if (cmType != null) + { + var connectionStringsProp = cmType.GetProperty("ConnectionStrings", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public); + var connectionStrings = connectionStringsProp?.GetValue(null); + if (connectionStrings != null) + { + // indexer "Item" with key + var indexer = connectionStrings.GetType().GetProperty("Item"); + var config = indexer?.GetValue(connectionStrings, new object[] { "default" }); + if (config != null) + { + var connStringProp = config.GetType().GetProperty("ConnectionString"); + connString = connStringProp?.GetValue(config) as string; + } + } + } + } + catch + { + // ignore any reflection failures and return null + } + + + return connString; + } + + + + + private void clearErrorMessages() + { + this.errorProviderMainForm.Clear(); + } + + /// + /// Form input validation + /// + /// Is documentation file required, applies when generating document + /// + private bool validateFormInput(bool requireDocumentationFile=true) + { + + bool errorFound = false; + + this.clearErrorMessages(); + + //was a connection string provided? + if (String.IsNullOrWhiteSpace(this.txtConnectionString.Text)) + { + errorFound = true; + this.errorProviderMainForm.SetError(this.txtConnectionString, "Please provide a connection string"); + } + else + { + var connectionTestResult = SqlConnectionTester.TestConnectionString(this.txtConnectionString.Text, true); + if (!connectionTestResult.Success) + { + errorFound = true; + this.errorProviderMainForm.SetError(this.txtConnectionString, (connectionTestResult.ErrorMessage ?? "Invalid connection string")); + } + + } + + //has an ouptut filename with optional path been provided? + if (requireDocumentationFile && String.IsNullOrWhiteSpace(this.txtDocFile.Text)) + { + errorFound = true; + this.errorProviderMainForm.SetError(this.txtDocFile, "Please enter a filename for the documentation file to be created"); + } + else + { + string proposedFilename = this.txtDocFile.Text.Trim(); + + FileInfo fileInfo = null; + + try + { + fileInfo = new FileInfo(proposedFilename); + } + catch + { + errorFound = true; + this.errorProviderMainForm.SetError(this.txtDocFile, "Proposed filename is invalid"); + } + + + //is an HTML file extension + if(fileInfo != null && !( fileInfo.Extension.ToLower() == ".htm" || fileInfo.Extension.ToLower() == ".html")) + { + errorFound = true; + this.errorProviderMainForm.SetError(this.txtDocFile, "File name must have .htm or .html file extension"); + } + + + } + + + return !errorFound; //input valid when no errors found + + } + + + + + private Task getDatabaseMetaData(string connectionString) + { + return Task.Run(() => + { + var dbi = new DatabaseInspector(connectionString); + return dbi.GetDatabaseMetaData(); + + }); + + } + + + private DocumentGeneratorConfiguration createDocumentGeneratorConfigurationFroUi() + { + var config = new DocumentGeneratorConfiguration(); + + config.ForeignKeyToTableHyperLink = this.chkFkToTableHyperLink.Checked; + + config.CheckForDesignIssues = this.chkCheckForDatabaseDesignIssues.Checked; + + return config; + } + + private async void btnGenerateDoc_Click(object sender, EventArgs e) + { + + + if (!this.validateFormInput(true)) + return; + this.toolStripStatusLabel.Text = "Working"; + this.toolStripProgressBar.Visible = true; + this.toolStripProgressBar.Style = ProgressBarStyle.Marquee; + + this.lockUi(); + + try + { + + string docFilePath = this.txtDocFile.Text.Trim(); + + + DocumentGeneratorConfiguration docGenConfig = this.createDocumentGeneratorConfigurationFroUi(); + + //perform database operations aysnc + var metadata = await getDatabaseMetaData(this.txtConnectionString.Text); + + await this.generateDocument(metadata, docFilePath, docGenConfig); + + this.toolStripProgressBar.Visible = false; + this.toolStripStatusLabel.Text = "Completed"; + + if (this.chkOpenDoc.Checked && File.Exists(docFilePath)) + Process.Start(docFilePath); + + } + catch(Exception ex) + { + MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK); + } + + this.unLockUi(); + } + + private async Task generateDocument(Database metadata, string docFilePath, DocumentGeneratorConfiguration docGenConfig) + { + var task = new Task(() => + { + + DatabaseHtmlDocumentGenerator gen = new DatabaseHtmlDocumentGenerator(); + using (var sw = new StreamWriter(docFilePath, false)) + { + var str = gen.ExportToHtml(metadata, sw, docGenConfig); + } + + + }); + + task.Start(); + await task; + } + + private void btnDocFileBrowse_Click(object sender, EventArgs e) + { + var dialog = new SaveFileDialog(); + dialog.AddExtension = true; + dialog.DefaultExt = "html"; + dialog.ValidateNames = true; + dialog.Filter = "HTML Files (*.htm;*.html)|*.htm;*.html"; + + if (dialog.ShowDialog() == DialogResult.OK) + { + this.txtDocFile.Text = dialog.FileName; + } + } + + private void btnEditConnection_Click(object sender, EventArgs e) + { + this.showConnectionStringDialog(); + } + + private void showConnectionStringDialog() + { + var frmConnEditDialog = new FrmConnectionString(this.txtConnectionString.Text.Trim()); + + frmConnEditDialog.ShowDialog(); + + if (frmConnEditDialog.Result == System.Windows.Forms.DialogResult.OK) + { + this.txtConnectionString.Text = frmConnEditDialog.GetConnectionString(); + } + + + + } + + private async void btnFindObjectsWithoutDescription_Click(object sender, EventArgs e) + { + if (!this.validateFormInput(false)) + return; + + + this.lockUi(); + + + //perform database operations aysnc + var metadata = await getDatabaseMetaData(this.txtConnectionString.Text); + + + this.showObjectsWithoutDescriptionDialog(metadata); + + this.unLockUi(); + } + + private void showObjectsWithoutDescriptionDialog(Database dbMetadata) + { + var dialogForm = new FrmObjectsWithoutDescription(dbMetadata); + dialogForm.ShowDialog(); + } + + /// + /// Lock UI controls + /// + /// Call before call long running logic + private void lockUi() + { + this.txtConnectionString.Enabled = false; + this.txtDocFile.Enabled = false; + this.btnDocFileBrowse.Enabled = false; + this.btnEditConnection.Enabled = false; + //this.btnGenerateDoc.Enabled = false; + this.btnFindObjectsWithoutDescription.Enabled = false; + this.chkOpenDoc.Enabled = false; + this.chkFkToTableHyperLink.Enabled = false; + this.chkCheckForDatabaseDesignIssues.Enabled = false; + } + + + /// + /// Unlock UI controls + /// + /// Call when locking UI no longer needed + private void unLockUi() + { + this.txtConnectionString.Enabled = true; + this.txtDocFile.Enabled = true; + this.btnDocFileBrowse.Enabled = true; + this.btnEditConnection.Enabled = true; + this.btnGenerateDoc.Enabled = true; + this.btnFindObjectsWithoutDescription.Enabled = true; + this.chkOpenDoc.Enabled = true; + this.chkFkToTableHyperLink.Enabled = true; + this.chkCheckForDatabaseDesignIssues.Enabled = true; + } + + } +} diff --git a/DocumentationGeneratorApplication/MainForm.resx b/DocumentationGeneratorApplication/MainForm.resx new file mode 100644 index 0000000..6c8f1e6 --- /dev/null +++ b/DocumentationGeneratorApplication/MainForm.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 196, 17 + + + 17, 17 + + + 283, 17 + + \ No newline at end of file diff --git a/DocumentationGeneratorApplication/Program.cs b/DocumentationGeneratorApplication/Program.cs new file mode 100644 index 0000000..aba2c7b --- /dev/null +++ b/DocumentationGeneratorApplication/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace net.datacowboy.DocumentationGeneratorApplication +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/DocumentationGeneratorApplication/Properties/AssemblyInfo.cs b/DocumentationGeneratorApplication/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..92cdcb3 --- /dev/null +++ b/DocumentationGeneratorApplication/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DocumentationGeneratorApplication")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("DocumentationGeneratorApplication")] +[assembly: AssemblyCopyright("Copyright © Jeremy Knue 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("aefb4aeb-f59d-4b3d-85c3-c5b6c382c5eb")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.4.1.0")] +//[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/DocumentationGeneratorApplication/Properties/Resources.Designer.cs b/DocumentationGeneratorApplication/Properties/Resources.Designer.cs new file mode 100644 index 0000000..5549a78 --- /dev/null +++ b/DocumentationGeneratorApplication/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace net.datacowboy.DocumentationGeneratorApplication.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("net.datacowboy.DocumentationGeneratorApplication.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/DocumentationGeneratorApplication/Properties/Resources.resx b/DocumentationGeneratorApplication/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/DocumentationGeneratorApplication/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DocumentationGeneratorApplication/Properties/Settings.Designer.cs b/DocumentationGeneratorApplication/Properties/Settings.Designer.cs new file mode 100644 index 0000000..fa5c634 --- /dev/null +++ b/DocumentationGeneratorApplication/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace net.datacowboy.DocumentationGeneratorApplication.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/DocumentationGeneratorApplication/Properties/Settings.settings b/DocumentationGeneratorApplication/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/DocumentationGeneratorApplication/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/DocumentationGeneratorApplication/third-party/BeTimvwFramework/Collections/Generic/PropertyComparer.cs b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/Collections/Generic/PropertyComparer.cs new file mode 100644 index 0000000..cee437b --- /dev/null +++ b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/Collections/Generic/PropertyComparer.cs @@ -0,0 +1,52 @@ +//This file comes from the Be.Timvw.Framework +//The Be.Timvw.Framework was created by Tim Van Wassenhove and is distributed under the Apache 2.0 license. +//http://betimvwframework.codeplex.com/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; + +namespace Be.Timvw.Framework.Collections.Generic +{ + public class PropertyComparer : IComparer + { + private readonly IComparer comparer; + private PropertyDescriptor propertyDescriptor; + private int reverse; + + public PropertyComparer(PropertyDescriptor property, ListSortDirection direction) + { + this.propertyDescriptor = property; + Type comparerForPropertyType = typeof(Comparer<>).MakeGenericType(property.PropertyType); + this.comparer = (IComparer)comparerForPropertyType.InvokeMember("Default", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.Public, null, null, null); + this.SetListSortDirection(direction); + } + + #region IComparer Members + + public int Compare(T x, T y) + { + return this.reverse * this.comparer.Compare(this.propertyDescriptor.GetValue(x), this.propertyDescriptor.GetValue(y)); + } + + #endregion + + private void SetPropertyDescriptor(PropertyDescriptor descriptor) + { + this.propertyDescriptor = descriptor; + } + + private void SetListSortDirection(ListSortDirection direction) + { + this.reverse = direction == ListSortDirection.Ascending ? 1 : -1; + } + + public void SetPropertyAndDirection(PropertyDescriptor descriptor, ListSortDirection direction) + { + this.SetPropertyDescriptor(descriptor); + this.SetListSortDirection(direction); + } + } +} \ No newline at end of file diff --git a/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/FilterList.cs b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/FilterList.cs new file mode 100644 index 0000000..531e304 --- /dev/null +++ b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/FilterList.cs @@ -0,0 +1,66 @@ +//This file comes from the Be.Timvw.Framework +//The Be.Timvw.Framework was created by Tim Van Wassenhove and is distributed under the Apache 2.0 license. +//http://betimvwframework.codeplex.com/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Be.Timvw.Framework.ComponentModel +{ + public class FilterList : SortableBindingList + { + readonly List allItems = new List(); + + public FilterList() + { + } + + public FilterList(IEnumerable elements) + : base(elements) + { + allItems.AddRange(elements); + } + + public void Filter(Predicate filter) + { + if (ReferenceEquals(filter, null)) throw new ArgumentNullException("filter"); + + ApplyFilter(filter); + if (IsSortedCore)ApplySortCore(SortPropertyCore, SortDirectionCore); + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected virtual void ApplyFilter(Predicate filter) + { + var wantedItems = this.allItems.FindAll(filter); + + Items.Clear(); + foreach (var item in wantedItems) Items.Add(item); + } + + protected override void InsertItem(int index, T item) + { + base.InsertItem(index, item); + allItems.Add(Items[index]); + } + + protected override void RemoveItem(int index) + { + allItems.Remove(Items[index]); + base.RemoveItem(index); + } + + protected override void ClearItems() + { + base.ClearItems(); + allItems.Clear(); + } + + protected override void SetItem(int index, T item) + { + allItems[allItems.IndexOf(Items[index])] = item; + base.SetItem(index, item); + } + } +} diff --git a/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/PropertyDescriptorHelper.cs b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/PropertyDescriptorHelper.cs new file mode 100644 index 0000000..3b8b569 --- /dev/null +++ b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/PropertyDescriptorHelper.cs @@ -0,0 +1,39 @@ +//This file comes from the Be.Timvw.Framework +//The Be.Timvw.Framework was created by Tim Van Wassenhove and is distributed under the Apache 2.0 license. +//http://betimvwframework.codeplex.com/ + +using System; +using System.ComponentModel; + +namespace Be.Timvw.Framework.ComponentModel +{ + public static class PropertyDescriptorHelper + { + public static PropertyDescriptor Get(object component, string propertyName) + { + PropertyDescriptor propertyDescriptor; + if (TryGet(component, propertyName, out propertyDescriptor)) + { + return propertyDescriptor; + } + + throw new ArgumentException(string.Format("The property '{0}' was not found.", propertyName)); + } + + public static bool TryGet(object component, string propertyName, out PropertyDescriptor propertyDescriptor) + { + PropertyDescriptorCollection propertyDescriptors = TypeDescriptor.GetProperties(component); + foreach (PropertyDescriptor aPropertyDescriptor in propertyDescriptors) + { + if (aPropertyDescriptor.Name == propertyName) + { + propertyDescriptor = aPropertyDescriptor; + return true; + } + } + + propertyDescriptor = null; + return false; + } + } +} diff --git a/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/SortableBindingList.cs b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/SortableBindingList.cs new file mode 100644 index 0000000..a33a3e9 --- /dev/null +++ b/DocumentationGeneratorApplication/third-party/BeTimvwFramework/ComponentModel/SortableBindingList.cs @@ -0,0 +1,102 @@ +//This file comes from the Be.Timvw.Framework +//The Be.Timvw.Framework was created by Tim Van Wassenhove and is distributed under the Apache 2.0 license. +//http://betimvwframework.codeplex.com/ + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Be.Timvw.Framework.Collections.Generic; + +namespace Be.Timvw.Framework.ComponentModel +{ + public class SortableBindingList : BindingList + { + private readonly Dictionary> comparers; + private bool isSorted; + private ListSortDirection listSortDirection; + private PropertyDescriptor propertyDescriptor; + + public SortableBindingList() + : base(new List()) + { + this.comparers = new Dictionary>(); + } + + public SortableBindingList(IEnumerable enumeration) + : base(new List(enumeration)) + { + this.comparers = new Dictionary>(); + } + + protected override bool SupportsSortingCore + { + get { return true; } + } + + protected override bool IsSortedCore + { + get { return this.isSorted; } + } + + protected override PropertyDescriptor SortPropertyCore + { + get { return this.propertyDescriptor; } + } + + protected override ListSortDirection SortDirectionCore + { + get { return this.listSortDirection; } + } + + protected override bool SupportsSearchingCore + { + get { return true; } + } + + protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) + { + List itemsList = (List)this.Items; + + Type propertyType = property.PropertyType; + PropertyComparer comparer; + if (!this.comparers.TryGetValue(propertyType, out comparer)) + { + comparer = new PropertyComparer(property, direction); + this.comparers.Add(propertyType, comparer); + } + + comparer.SetPropertyAndDirection(property, direction); + itemsList.Sort(comparer); + + this.propertyDescriptor = property; + this.listSortDirection = direction; + this.isSorted = true; + + this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected override void RemoveSortCore() + { + this.isSorted = false; + this.propertyDescriptor = base.SortPropertyCore; + this.listSortDirection = base.SortDirectionCore; + + this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected override int FindCore(PropertyDescriptor property, object key) + { + int count = this.Count; + for (int i = 0; i < count; ++i) + { + T element = this[i]; + if (property.GetValue(element).Equals(key)) + { + return i; + } + } + + return -1; + } + } +} \ No newline at end of file diff --git a/DocumentationGeneratorConsole/App.config b/DocumentationGeneratorConsole/App.config new file mode 100644 index 0000000..58262a1 --- /dev/null +++ b/DocumentationGeneratorConsole/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/DocumentationGeneratorConsole/DocumentationGeneratorConsole.csproj b/DocumentationGeneratorConsole/DocumentationGeneratorConsole.csproj new file mode 100644 index 0000000..a736c1a --- /dev/null +++ b/DocumentationGeneratorConsole/DocumentationGeneratorConsole.csproj @@ -0,0 +1,20 @@ + + + + + net40 + Exe + net.datacowboy.DocumentationGeneratorConsole + DocumentationGeneratorConsole + false + + + + + + + + + + + \ No newline at end of file diff --git a/DocumentationGeneratorConsole/Program.cs b/DocumentationGeneratorConsole/Program.cs new file mode 100644 index 0000000..60bc5a0 --- /dev/null +++ b/DocumentationGeneratorConsole/Program.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using System.IO; +using System.Data.SqlClient; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Document; + +namespace net.datacowboy.DocumentationGeneratorConsole +{ + class Program + { + static void Main(string[] args) + { + + //TODO: add param support + string connStr = "Server=.; Database=AdventureWorks; Integrated Security=true;"; + + DatabaseInspector dbi = new DatabaseInspector(connStr); + var metadata = dbi.GetDatabaseMetaData(); + + + + DatabaseHtmlDocumentGenerator gen = new DatabaseHtmlDocumentGenerator(); + + using (var sw = new StreamWriter("database.html", false)) + { + var str = gen.ExportToHtml(metadata, sw); + } + + Console.ReadLine(); + + } + } +} diff --git a/DocumentationGeneratorConsole/Properties/AssemblyInfo.cs b/DocumentationGeneratorConsole/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..43d2c70 --- /dev/null +++ b/DocumentationGeneratorConsole/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("DocumentationGeneratorConsole")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jeremy Knue")] +[assembly: AssemblyProduct("DocumentationGeneratorConsole")] +[assembly: AssemblyCopyright("Copyright © Jeremy Knue 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e642ca46-7a48-4d4b-8ed8-73acf0e7ed60")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +//[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..1a8f3d5 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,41 @@ +This project (SqlServerDatabaseDocumentationGenerator) is released under the MIT license. + +The MIT License (MIT) + +Copyright (c) 2014 Jeremy Knue + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +This project contains the following third party components. + +PetaPoco +.NET ORM Library +PetaPoco was created by TopTenSoftware and is distibuted under the Apache 2.0 license. +http://www.toptensoftware.com/petapoco/ + +Bootstrap +Cascading style sheets code +Bootstrap was created by Twitter and is distributed under the MIT license. +http://getbootstrap.com/ + +Be.Timvw.Framework +Portions of the Be.Timvw.Framework are used +The Be.Timvw.Framework was created by Tim Van Wassenhove and is distributed under the Apache 2.0 license. +http://betimvwframework.codeplex.com/ \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..e0f3f71 --- /dev/null +++ b/Readme.md @@ -0,0 +1,11 @@ +This project will allow you to create human readable documentation for a Microsoft SQL Server database (versions 2005+). This includes schemas, tables, views, columns, functions, and stored procedures. + +Descriptions for objects are taken from extended properties with a name 'MS_Description' such as those created when descriptions provided to database objects using SQL Management Studio. + +To use this project build it using Visual Studio. The projects are currently using .NET 4.8 but should work on 4.5 or later. Then run the DocumentationGeneratorApplication program providing a connection string to your database and supplying a file name for the resulting documentation file. The file will be an HTML file. + +An example of the resulting documentation file can be viewed at http://jeremykdev.github.io/SqlServerDatabaseDocumentationGenerator/AdventureWorks-sample.html + +In addition to creating documentation the application can help you identify objects (tables, views, etc.) without a description in the database metabase and create the SQL script to add descriptions. + +Version 1.4 adds detection of Microsoft database design guideline violations such as detecting objects named using reserves keywords and user defined stored procedures named starting with 'sp_*'. \ No newline at end of file diff --git a/SqlServerDatabaseDocumentationGenerator.sln b/SqlServerDatabaseDocumentationGenerator.sln new file mode 100644 index 0000000..fa3e59a --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlServerDatabaseDocumentationGenerator", "SqlServerDatabaseDocumentationGenerator\SqlServerDatabaseDocumentationGenerator.csproj", "{68BB69BF-A89E-4A81-B2B7-160ACE34E0F7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocumentationGeneratorApplication", "DocumentationGeneratorApplication\DocumentationGeneratorApplication.csproj", "{5FF70C2D-FDE3-47F6-A95C-605C98CE1996}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{550C3EA5-F313-4918-A867-AF7518BB55F1}" + ProjectSection(SolutionItems) = preProject + AdventureWorks-example.html = AdventureWorks-example.html + LICENSE.txt = LICENSE.txt + Readme.md = Readme.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlServerDatabaseDocumentationGeneratorTest", "SqlServerDatabaseDocumentationGeneratorTest\SqlServerDatabaseDocumentationGeneratorTest.csproj", "{1EF99352-90C4-4AD3-B45A-439BF2E25633}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {68BB69BF-A89E-4A81-B2B7-160ACE34E0F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68BB69BF-A89E-4A81-B2B7-160ACE34E0F7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68BB69BF-A89E-4A81-B2B7-160ACE34E0F7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68BB69BF-A89E-4A81-B2B7-160ACE34E0F7}.Release|Any CPU.Build.0 = Release|Any CPU + {5FF70C2D-FDE3-47F6-A95C-605C98CE1996}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FF70C2D-FDE3-47F6-A95C-605C98CE1996}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FF70C2D-FDE3-47F6-A95C-605C98CE1996}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FF70C2D-FDE3-47F6-A95C-605C98CE1996}.Release|Any CPU.Build.0 = Release|Any CPU + {1EF99352-90C4-4AD3-B45A-439BF2E25633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EF99352-90C4-4AD3-B45A-439BF2E25633}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EF99352-90C4-4AD3-B45A-439BF2E25633}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EF99352-90C4-4AD3-B45A-439BF2E25633}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/SqlServerDatabaseDocumentationGenerator/Document/DatabaseHtmlDocumentGenerator.cs b/SqlServerDatabaseDocumentationGenerator/Document/DatabaseHtmlDocumentGenerator.cs new file mode 100644 index 0000000..774dfdd --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Document/DatabaseHtmlDocumentGenerator.cs @@ -0,0 +1,1628 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.IO; +using System.Web.UI; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +// Provide a lightweight fallback for System.Web.UI.HtmlTextWriter and related types when System.Web is not referenced. +// This allows the project to compile in environments where System.Web (and its assemblies) are unavailable. +// The fallback implements only the methods and enums used by this file. +namespace System.Web.UI +{ + public enum HtmlTextWriterTag + { + Html, Head, Title, Style, Body, H1, P, Hr, H2, Table, Thead, Tr, Th, Tbody, Td, A, Caption, H3, Span + } + + public enum HtmlTextWriterAttribute + { + Class, Type, Href, Style + } + + public class HtmlTextWriter : System.IO.TextWriter + { + private readonly System.IO.TextWriter _writer; + private readonly System.Collections.Generic.Stack _tagStack = new System.Collections.Generic.Stack(); + private readonly System.Text.StringBuilder _attrBuilder = new System.Text.StringBuilder(); + + public HtmlTextWriter(System.IO.TextWriter writer) + { + _writer = writer ?? throw new System.ArgumentNullException(nameof(writer)); + } + + public override System.Text.Encoding Encoding => _writer.Encoding; + + public void AddAttribute(HtmlTextWriterAttribute attr, string value) + { + AddAttribute(attr.ToString().ToLowerInvariant(), value); + } + + public void AddAttribute(string name, string value) + { + if (string.IsNullOrEmpty(value)) return; + // encode attribute values to avoid broken HTML + _attrBuilder.Append(' '); + _attrBuilder.Append(name); + _attrBuilder.Append("=\""); + _attrBuilder.Append(System.Net.WebUtility.HtmlEncode(value)); + _attrBuilder.Append('"'); + } + + public override void WriteLine(string s) + { + _writer.WriteLine(s); + } + + public override void Write(char value) + { + _writer.Write(value); + } + + public override void Write(string s) + { + _writer.Write(s); + } + + public void WriteEncodedText(string s) + { + _writer.Write(System.Net.WebUtility.HtmlEncode(s ?? string.Empty)); + } + + public void RenderBeginTag(HtmlTextWriterTag tag) + { + var tagName = tag.ToString().ToLowerInvariant(); + + // treat hr as self-closing + if (tag == HtmlTextWriterTag.Hr) + { + _writer.Write(" 0) + { + _writer.Write(_attrBuilder.ToString()); + _attrBuilder.Clear(); + } + _writer.Write(" />"); + return; + } + + _writer.Write('<'); + _writer.Write(tagName); + if (_attrBuilder.Length > 0) + { + _writer.Write(_attrBuilder.ToString()); + _attrBuilder.Clear(); + } + _writer.Write('>'); + _tagStack.Push(tagName); + } + + public void RenderEndTag() + { + if (_tagStack.Count == 0) return; + var tagName = _tagStack.Pop(); + _writer.Write("'); + } + + public void WriteBreak() + { + _writer.Write("
"); + } + } +} + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Document +{ + public class DatabaseHtmlDocumentGenerator + { + //http://stackoverflow.com/questions/1559791/how-do-i-find-assembly-version-of-calling-program + private readonly string appVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(3); + + private readonly string projectUrl = "http://jeremykdev.github.io/SqlServerDatabaseDocumentationGenerator/"; + + public string ExportToHtml(Database db, TextWriter textWriter, DocumentGeneratorConfiguration docGenConfig) + { + if (db == null) + { + throw new ArgumentNullException("db"); + } + + if (textWriter == null) + { + throw new ArgumentNullException("textWriter"); + } + + if (docGenConfig == null) + { + throw new ArgumentNullException("docGenConfig"); + } + + + //var sw = new StringWriter(); + + using(var hw = new HtmlTextWriter(textWriter)) + { + hw.WriteLine(""); + hw.RenderBeginTag(HtmlTextWriterTag.Html); + + hw.RenderBeginTag(HtmlTextWriterTag.Head); + + hw.RenderBeginTag(HtmlTextWriterTag.Title); + + hw.WriteEncodedText(db.DatabaseName); //TODO: does Write method escape HTML entities??? + + hw.RenderEndTag(); //title + + + hw.Write(""); + hw.AddAttribute(HtmlTextWriterAttribute.Type, "text/css"); + hw.RenderBeginTag(HtmlTextWriterTag.Style); + hw.Write(this.baseCss); + hw.RenderEndTag(); //style + + hw.RenderEndTag(); //head + + hw.RenderBeginTag(HtmlTextWriterTag.Body); + + hw.RenderBeginTag(HtmlTextWriterTag.H1); + + hw.WriteEncodedText(String.Format("{0} Database", db.DatabaseName)); + + hw.RenderEndTag(); //h1 + + if (!String.IsNullOrWhiteSpace(db.Description)) + { + hw.RenderBeginTag(HtmlTextWriterTag.P); + hw.WriteEncodedText(db.Description); + hw.RenderEndTag(); //p + } + + if (db.Schemas != null && db.Schemas.Count > 0) + { + + for (int s = 0; s < db.Schemas.Count; s++) + { + var schema = db.Schemas[s]; + bool hasTables = (schema.Tables != null && schema.Tables.Count > 0); + + if (s > 0) + { + hw.RenderBeginTag(HtmlTextWriterTag.Hr); + hw.RenderEndTag(); //hr + } + + hw.RenderBeginTag(HtmlTextWriterTag.H2); + hw.WriteEncodedText(String.Format("{0} (schema)", schema.SchemaName)); + hw.RenderEndTag(); //h2 + + if (!String.IsNullOrWhiteSpace(schema.Description)) + { + hw.RenderBeginTag(HtmlTextWriterTag.P); + hw.WriteEncodedText(schema.Description); + hw.RenderEndTag(); //p + + } + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed schema-objects-list-container"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Object Type"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Count"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.Write("Tables"); + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + if (hasTables) + { + hw.WriteEncodedText(schema.Tables.Count.ToString()); + } + else + { + hw.Write("0"); + } + + hw.RenderEndTag(); //td + hw.RenderEndTag(); //tr + + + #region Total Table Fields Summary + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.Write("Fields (all tables)"); + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + if (hasTables) + { + // compute total number of fields (columns) in all tables + int totalTableFields = 0; + if (db.Schemas != null) + { + totalTableFields = db.Schemas.Sum(sch => (sch.Tables != null) ? sch.Tables.Sum(tbl => (tbl.Columns != null) ? tbl.Columns.Count : 0) : 0); + } + hw.WriteEncodedText(totalTableFields.ToString()); + } + else + { + hw.Write("0"); + } + + hw.RenderEndTag(); //td + hw.RenderEndTag(); //tr + + #endregion + + + + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.Write("Views"); + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + if (schema.Views != null) + { + hw.WriteEncodedText(schema.Views.Count.ToString()); + } + else + { + hw.Write("0"); + } + + hw.RenderEndTag(); //td + + + hw.RenderEndTag(); //tr + + + + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.Write("Stored procedures"); + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + if (schema.StoredProcedures != null) + { + hw.WriteEncodedText(schema.StoredProcedures.Count.ToString()); + } + else + { + hw.Write("0"); + } + + hw.RenderEndTag(); //td + + + hw.RenderEndTag(); //tr + + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.Write("Scalar functions"); + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + if (schema.ScalarFunctions != null) + { + hw.WriteEncodedText(schema.ScalarFunctions.Count.ToString()); + } + else + { + hw.Write("0"); + } + + hw.RenderEndTag(); //td + + + hw.RenderEndTag(); //tr + + + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.Write("Table functions"); + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + if (schema.TableFunctions != null) + { + hw.WriteEncodedText(schema.TableFunctions.Count.ToString()); + } + else + { + hw.Write("0"); + } + + hw.RenderEndTag(); //td + + + hw.RenderEndTag(); //tr + + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + + //Render tables + if (hasTables) + { + + #region Tables Records (Rows) + + + + hw.RenderBeginTag(HtmlTextWriterTag.Hr); + hw.RenderEndTag(); //hr + + + hw.RenderBeginTag(HtmlTextWriterTag.H2); + hw.WriteEncodedText("Tables Records"); + hw.RenderEndTag(); //h2 + + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed schema-objects-list-container"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Table Name"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Records (Rows)"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + + int totalTableRecords = 0; + + for (int t = 0; t < schema.Tables.Count; t++) + { + var table = schema.Tables[t]; + if (table.RowCount > 0) + { + totalTableRecords += table.RowCount; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + + bool createHyperlink = true; + if (createHyperlink) + { + // create internel hyperlink target + hw.AddAttribute("href", String.Concat("#_", table.ObjectId.ToString())); + hw.RenderBeginTag(HtmlTextWriterTag.A); + hw.WriteEncodedText(table.TableName); + hw.RenderEndTag(); //a + } + else + { + hw.Write(table.TableName); + } + + + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-left"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(table.RowCount.ToString("N0")); + + hw.RenderEndTag(); //td + hw.RenderEndTag(); //tr + } + } + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-center"); + hw.AddAttribute(HtmlTextWriterAttribute.Style, "font-weight:bold;"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.Write("Total records"); + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-center"); + hw.AddAttribute(HtmlTextWriterAttribute.Style, "font-weight:bold;"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(totalTableRecords.ToString("N0")); + + hw.RenderEndTag(); //td + hw.RenderEndTag(); //tr + + + hw.RenderEndTag(); //tbody + hw.RenderEndTag(); //table + + + #endregion + + + for (int t = 0; t < schema.Tables.Count; t++) + { + var table = schema.Tables[t]; + + if (docGenConfig.ForeignKeyToTableHyperLink) + { + // create internel hyperlink target + hw.AddAttribute("id", table.GetObjectAnchorId()); + hw.RenderBeginTag(HtmlTextWriterTag.A); + hw.RenderEndTag(); //a + } + + bool hasIndexes = (table.Indexes != null && table.Indexes.Count > 0); + + hw.RenderBeginTag(HtmlTextWriterTag.H3); + hw.WriteEncodedText(String.Format("{0}.{1} (table)", schema.SchemaName, table.TableName)); + hw.RenderEndTag(); //h3 + + + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + if (!String.IsNullOrWhiteSpace(table.Description)) + { + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.WriteEncodedText(table.Description); + hw.RenderEndTag(); //caption + } + + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Column Name"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("System Data Type"); + + if (table.ContainsColumnsWithUserDefinedDataType()) + { + hw.Write(" / User Defined Type"); + } + + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Size"); + hw.RenderEndTag(); //th + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Allow Null"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Default"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Is Computed"); + hw.RenderEndTag(); //th + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Description"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int c = 0; c < table.Columns.Count; c++) + { + + + var col = table.Columns[c]; + + bool colHasDefaultValue = (col.DefaultValue != null); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.ColumnName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.BaseDataTypeName); + if (col.IsIdentity) + { + hw.WriteEncodedText(" (identity)"); + } + + if (col.IsUserDefinedType) + { + hw.Write(" / "); + hw.WriteEncodedText(col.TypeName); + } + + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + + //show precision, scale when appliable, else use maxlength + if (col.Precision.HasValue && col.Scale.HasValue && col.Precision > 0) + { + hw.WriteEncodedText(String.Format("{0},{1}",col.Precision.Value, col.Scale.Value)); + } + else + { + if (col.MaximumLength.HasValue && col.MaximumLength.Value != -1) + { + hw.WriteEncodedText(col.MaximumLength.ToString()); + } + + } + + + hw.RenderEndTag(); //td + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.AllowNull ? "Yes" : "No"); + hw.RenderEndTag(); //td + + + + if (!colHasDefaultValue) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "no-default"); + } + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(colHasDefaultValue ? col.DefaultValue : "none"); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.IsComputed ? "Yes" : "No"); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.Description ?? String.Empty); + hw.RenderEndTag(); //td + + hw.RenderEndTag(); //tr + + } //for column loop + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + if (hasIndexes) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.WriteEncodedText(String.Format("Indexes on {0}", table.TableName)); + hw.RenderEndTag(); //caption + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Index Name"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Description"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Is Primary Key"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Is Unique"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Index Type"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Columns"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + hw.RenderEndTag(); //thead + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int i = 0; i < table.Indexes.Count; i++) + { + var index = table.Indexes[i]; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.IndexName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.Description ?? String.Empty); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.IsPrimaryKey.ToYesNo()); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.IsUnique.ToYesNo()); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.IndexTypeDescription); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(String.Join(", ", index.ColumnNames.ToArray())); + hw.RenderEndTag(); //td + + + hw.RenderEndTag(); //tr + } + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + } //end indexes + + + + if (table.ForeignKeys != null && table.ForeignKeys.Count > 0) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.WriteEncodedText(String.Format("Foreign Keys in {0}", table.TableName)); + hw.RenderEndTag(); //caption + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Foreign Key Name"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Columns"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("References Table (Columns)"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Description"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //thead + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int f = 0; f < table.ForeignKeys.Count; f++) + { + var fk = table.ForeignKeys[f]; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(fk.ForeignKeyName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(String.Join(", ", fk.GetForeignKeyParentColumnNames())); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + //hw.WriteEncodedText(String.Format("{0}.{1} ({2})", fk.ReferencedObjectSchemaName, fk.ReferencedObjectName, String.Join(", ",fk.GetForeignKeyReferenceColumnNames()))); + + + if (docGenConfig.ForeignKeyToTableHyperLink) + { + hw.AddAttribute("href", fk.GetFkTargetAnchorId()); + hw.RenderBeginTag(HtmlTextWriterTag.A); + hw.WriteEncodedText(String.Format("{0}.{1} ({2})", fk.ReferencedObjectSchemaName, fk.ReferencedObjectName, String.Join(", ", fk.GetForeignKeyReferenceColumnNames()))); + //hw.WriteEncodedText("table ->"); + hw.RenderEndTag(); //a + } + else + { + hw.WriteEncodedText(String.Format("{0}.{1} ({2})", fk.ReferencedObjectSchemaName, fk.ReferencedObjectName, String.Join(", ", fk.GetForeignKeyReferenceColumnNames()))); + } + + hw.RenderEndTag(); //td + + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(fk.Description ?? String.Empty); + hw.RenderEndTag(); //td + + hw.RenderEndTag(); //tr + } + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + + } //end foreign keys + + } //end for table loop + + + + + + + } + + + if (schema.Views != null && schema.Views.Count > 0) + { + for (int v = 0; v < schema.Views.Count; v++) + { + var view = schema.Views[v]; + + + + hw.RenderBeginTag(HtmlTextWriterTag.H3); + hw.WriteEncodedText(String.Format("{0}.{1} (view)", schema.SchemaName, view.ViewName)); + hw.RenderEndTag(); //h3 + + //TODO: add columns and indexes for view, use view.Description as table caption + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + if (!String.IsNullOrWhiteSpace(view.Description)) + { + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.WriteEncodedText(view.Description); + hw.RenderEndTag(); //caption + } + + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Column Name"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("System Data Type"); + + if (view.ContainsColumnsWithUserDefinedDataType()) + { + hw.Write(" / User Defined Type"); + } + + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Size"); + hw.RenderEndTag(); //th + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Allow Null"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Default"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Is Computed"); + hw.RenderEndTag(); //th + + + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int c = 0; c < view.Columns.Count; c++) + { + + + var col = view.Columns[c]; + + bool colHasDefaultValue = (col.DefaultValue != null); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.ColumnName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.BaseDataTypeName); + if (col.IsIdentity) + { + hw.WriteEncodedText(" (identity)"); + } + + if (col.IsUserDefinedType) + { + hw.Write(" / "); + hw.WriteEncodedText(col.TypeName); + } + hw.RenderEndTag(); //td + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + //show precision, scale when appliable + if (col.Precision.HasValue && col.Scale.HasValue && col.Precision > 0) + { + hw.WriteEncodedText(String.Format("{0},{1}",col.Precision.Value, col.Scale.Value)); + } + else + { + if (col.MaximumLength.HasValue && col.MaximumLength.Value != -1) + { + hw.WriteEncodedText(col.MaximumLength.ToString()); + } + //else + //{ + // hw.Write("n/a"); + //} + } + + + hw.RenderEndTag(); //td + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.AllowNull.ToYesNo()); + hw.RenderEndTag(); //td + + + + if (!colHasDefaultValue) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "no-default"); + } + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(colHasDefaultValue ? col.DefaultValue : "none"); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.IsComputed.ToYesNo()); + hw.RenderEndTag(); //td + + + + hw.RenderEndTag(); //tr + + } //for column loop + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + if (view.IsIndexedView) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.WriteEncodedText(String.Format("Indexes on {0}", view.ViewName)); + hw.RenderEndTag(); //caption + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Index Name"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Description"); + hw.RenderEndTag(); //th + + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Is Unique"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Index Type"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Columns"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + hw.RenderEndTag(); //thead + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int i = 0; i < view.Indexes.Count; i++) + { + var index = view.Indexes[i]; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.IndexName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.Description ?? String.Empty); + hw.RenderEndTag(); //td + + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.IsUnique.ToYesNo()); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(index.IndexTypeDescription); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(String.Join(", ", index.ColumnNames.ToArray())); + hw.RenderEndTag(); //td + + + hw.RenderEndTag(); //tr + } + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + } //end indexes + + + } //end views loop + + + }//end views + + + + if (schema.StoredProcedures != null && schema.StoredProcedures.Count > 0) + { + for (int r = 0; r < schema.StoredProcedures.Count; r++) + { + var sproc = schema.StoredProcedures[r]; + + hw.RenderBeginTag(HtmlTextWriterTag.H3); + hw.WriteEncodedText(String.Format("{0}.{1} (stored procedure)", schema.SchemaName,sproc.ProcedureName) ); + hw.RenderEndTag(); //h3 + + if (!String.IsNullOrWhiteSpace(sproc.Description)) + { + hw.RenderBeginTag(HtmlTextWriterTag.P); + hw.WriteEncodedText(sproc.Description); + hw.RenderEndTag(); //p + } + + if (sproc.Parameters != null && sproc.Parameters.Count > 0) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.Write("Parameters"); + hw.RenderEndTag();//caption + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Paramater Name"); + hw.RenderEndTag(); //th + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("System Data Type"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Size"); + hw.RenderEndTag(); //th + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Direction"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Description"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int m = 0; m < sproc.Parameters.Count; m++) + { + var param = sproc.Parameters[m]; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.ParameterName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.DataType); + hw.RenderEndTag(); //td + + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + //show precision, scale when appliable + if (param.Precision.HasValue && param.Scale.HasValue && param.Precision > 0) + { + hw.WriteEncodedText(String.Format("{0},{1}", param.Precision.Value, param.Scale.Value)); + } + else + { + if (param.MaximumLength.HasValue && param.MaximumLength.Value != -1) + { + hw.WriteEncodedText(param.MaximumLength.ToString()); + } + //else + //{ + // hw.Write("n/a"); + //} + } + + + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.Direction); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.Description ?? String.Empty); + hw.RenderEndTag(); //td + + hw.RenderEndTag(); //tr + + } //end parameter loop + + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + + } //end sproc parameters + + + } //end sproc loop + + + + }// end stored procedures + + + + if (schema.ScalarFunctions != null && schema.ScalarFunctions.Count > 0) + { + for (int f = 0; f < schema.ScalarFunctions.Count; f++) + { + var func = schema.ScalarFunctions[f]; + + hw.RenderBeginTag(HtmlTextWriterTag.H3); + hw.WriteEncodedText(String.Format("{0}.{1} (scalar function)", schema.SchemaName, func.FunctionName)); + hw.RenderEndTag(); //h3 + + if (!String.IsNullOrWhiteSpace(func.Description)) + { + hw.RenderBeginTag(HtmlTextWriterTag.P); + hw.WriteEncodedText(func.Description); + hw.RenderEndTag(); //p + } + + if (!String.IsNullOrEmpty(func.ReturnDataType)) + { + + + hw.RenderBeginTag(HtmlTextWriterTag.P); + hw.Write("Returns "); + hw.AddAttribute(HtmlTextWriterAttribute.Class, "return-type"); + hw.RenderBeginTag(HtmlTextWriterTag.Span); + hw.WriteEncodedText(func.GetReturnTypeDisplayText()); + hw.RenderEndTag(); //span + hw.RenderEndTag(); //p + + } + + + if (func.Parameters != null && func.Parameters.Count > 0) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.Write("Parameters"); + hw.RenderEndTag();//caption + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Paramater Name"); + hw.RenderEndTag(); //th + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("System Data Type"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Size"); + hw.RenderEndTag(); //th + + + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Description"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int m = 0; m < func.Parameters.Count; m++) + { + var param = func.Parameters[m]; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.ParameterName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.DataType); + hw.RenderEndTag(); //td + + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + //show precision, scale when appliable + if (param.Precision.HasValue && param.Scale.HasValue && param.Precision > 0) + { + hw.WriteEncodedText(String.Format("{0},{1}", param.Precision.Value, param.Scale.Value)); + } + else + { + if (param.MaximumLength.HasValue && param.MaximumLength.Value != -1) + { + hw.WriteEncodedText(param.MaximumLength.ToString()); + } + + } + + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.Description ?? String.Empty); + hw.RenderEndTag(); //td + + hw.RenderEndTag(); //tr + + } //end scalar func parameter loop + + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + + } //end scalar params + } + + + } //end scalar functions + + + if (schema.TableFunctions != null && schema.TableFunctions.Count > 0) + { + for (int t = 0; t < schema.TableFunctions.Count; t++) + { + var tFunc = schema.TableFunctions[t]; + + hw.RenderBeginTag(HtmlTextWriterTag.H3); + hw.WriteEncodedText(String.Format("{0}.{1} (table function)", schema.SchemaName, tFunc.FunctionName)); + hw.RenderEndTag(); //h3 + + if (!String.IsNullOrWhiteSpace(tFunc.Description)) + { + hw.RenderBeginTag(HtmlTextWriterTag.P); + hw.WriteEncodedText(tFunc.Description); + hw.RenderEndTag(); //p + } + + + if (tFunc.Parameters != null && tFunc.Parameters.Count > 0) + { + if (tFunc.Parameters != null && tFunc.Parameters.Count > 0) + { + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Caption); + hw.Write("Parameters"); + hw.RenderEndTag();//caption + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Paramater Name"); + hw.RenderEndTag(); //th + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("System Data Type"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Size"); + hw.RenderEndTag(); //th + + + + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Description"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int m = 0; m < tFunc.Parameters.Count; m++) + { + var param = tFunc.Parameters[m]; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.ParameterName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.DataType); + hw.RenderEndTag(); //td + + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "text-right"); + hw.RenderBeginTag(HtmlTextWriterTag.Td); + //show precision, scale when appliable + if (param.Precision.HasValue && param.Scale.HasValue && param.Precision > 0) + { + hw.WriteEncodedText(String.Format("{0},{1}", param.Precision.Value, param.Scale.Value)); + } + else + { + if (param.MaximumLength.HasValue && param.MaximumLength.Value != -1) + { + hw.WriteEncodedText(param.MaximumLength.ToString()); + } + + } + + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(param.Description ?? String.Empty); + hw.RenderEndTag(); //td + + hw.RenderEndTag(); //tr + + } //end table func parameter loop + + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + + + } + + }//end table function parameter + + + if (tFunc.Columns != null && tFunc.Columns.Count > 0) + { + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed schema-objects-list-container"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Column Name"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("System Data Type"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Allow Null"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + for (int c = 0; c < tFunc.Columns.Count; c++) + { + var col = tFunc.Columns[c]; + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.ColumnName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.BaseDataTypeName); + hw.RenderEndTag(); //td + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(col.AllowNull.ToYesNo()); + hw.RenderEndTag(); //td + + hw.RenderEndTag(); //tr + + } //end table function column loop + + hw.RenderEndTag(); //tbody + + hw.RenderEndTag(); //table + + } //end table function columns + + + } + + } //end table functions + + }// end for schema loop + + } + + + //display design issues + if (docGenConfig.CheckForDesignIssues && db.DesignIssueWarnings.HasAny()) + { + hw.RenderBeginTag(HtmlTextWriterTag.H2); + hw.WriteEncodedText(String.Format("Design Issue Warnings ({0})", db.DesignIssueWarnings.Count)); + hw.RenderEndTag(); //h2 + + hw.AddAttribute(HtmlTextWriterAttribute.Class, "table table-bordered table-striped table-condensed"); + hw.RenderBeginTag(HtmlTextWriterTag.Table); + + hw.RenderBeginTag(HtmlTextWriterTag.Thead); + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Warning Description"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Reference URL"); + hw.RenderEndTag(); //th + + hw.RenderBeginTag(HtmlTextWriterTag.Th); + hw.Write("Object(s)"); + hw.RenderEndTag(); //th + + hw.RenderEndTag(); //tr + + hw.RenderEndTag(); //thead + + hw.RenderBeginTag(HtmlTextWriterTag.Tbody); + + foreach(var warn in db.DesignIssueWarnings) + { + + hw.RenderBeginTag(HtmlTextWriterTag.Tr); + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + hw.WriteEncodedText(warn.Description); + hw.RenderEndTag(); //td + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + if(warn.ReferenceUrl != null) + { + hw.AddAttribute("href", warn.ReferenceUrl.ToString()); + hw.RenderBeginTag(HtmlTextWriterTag.A); + hw.WriteEncodedText(warn.ReferenceUrl.ToString()); + hw.RenderEndTag(); //a + } + hw.RenderEndTag(); //td + + + hw.RenderBeginTag(HtmlTextWriterTag.Td); + foreach(IDbObject obj in warn.DatabaseObjects) + { + //hw.WriteEncodedText(obj.ObjectFullDisplayName); //TODO: add internal hyperlinking + hw.WriteEncodedText(String.Format("{0} ( {1} )", obj.ObjectFullDisplayName, obj.ObjectTypeDisplayText)); + hw.WriteBreak(); //br + + } + //hw.WriteEncodedText(warn.Description); + hw.RenderEndTag(); //td + + hw.RenderEndTag(); //tr + } + + hw.RenderEndTag(); // tbody + + hw.RenderEndTag(); //table + } + + //footer + hw.RenderBeginTag(HtmlTextWriterTag.P); + hw.WriteEncodedText(String.Format("Documentation created using SQL Server Database Documentation Generator version {0}", this.appVersion)); + + hw.WriteBreak(); //br + + hw.AddAttribute(HtmlTextWriterAttribute.Href, this.projectUrl); + hw.RenderBeginTag(HtmlTextWriterTag.A); + hw.WriteEncodedText(this.projectUrl); + hw.RenderEndTag(); //a + + hw.RenderEndTag(); //p + + + + hw.RenderEndTag(); //body + + hw.RenderEndTag(); //html + } + + return textWriter.ToString(); + } + + + //bootstrip + custom css + private readonly string baseCss = @" + html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:"" ("" attr(href) "")""}abbr[title]:after{content:"" ("" attr(title) "")""}a[href^=""javascript:""]:after,a[href^=""#""]:after{content:""""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:""Helvetica Neue"",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}.list-inline>li:first-child{padding-left:0}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.428571429;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""""}address{margin-bottom:20px;font-style:normal;line-height:1.428571429}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,""Courier New"",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.428571429;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=date]{line-height:34px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.has-feedback .form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.428571429;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:400;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:""\2a""}.glyphicon-plus:before{content:""\2b""}.glyphicon-euro:before{content:""\20ac""}.glyphicon-minus:before{content:""\2212""}.glyphicon-cloud:before{content:""\2601""}.glyphicon-envelope:before{content:""\2709""}.glyphicon-pencil:before{content:""\270f""}.glyphicon-glass:before{content:""\e001""}.glyphicon-music:before{content:""\e002""}.glyphicon-search:before{content:""\e003""}.glyphicon-heart:before{content:""\e005""}.glyphicon-star:before{content:""\e006""}.glyphicon-star-empty:before{content:""\e007""}.glyphicon-user:before{content:""\e008""}.glyphicon-film:before{content:""\e009""}.glyphicon-th-large:before{content:""\e010""}.glyphicon-th:before{content:""\e011""}.glyphicon-th-list:before{content:""\e012""}.glyphicon-ok:before{content:""\e013""}.glyphicon-remove:before{content:""\e014""}.glyphicon-zoom-in:before{content:""\e015""}.glyphicon-zoom-out:before{content:""\e016""}.glyphicon-off:before{content:""\e017""}.glyphicon-signal:before{content:""\e018""}.glyphicon-cog:before{content:""\e019""}.glyphicon-trash:before{content:""\e020""}.glyphicon-home:before{content:""\e021""}.glyphicon-file:before{content:""\e022""}.glyphicon-time:before{content:""\e023""}.glyphicon-road:before{content:""\e024""}.glyphicon-download-alt:before{content:""\e025""}.glyphicon-download:before{content:""\e026""}.glyphicon-upload:before{content:""\e027""}.glyphicon-inbox:before{content:""\e028""}.glyphicon-play-circle:before{content:""\e029""}.glyphicon-repeat:before{content:""\e030""}.glyphicon-refresh:before{content:""\e031""}.glyphicon-list-alt:before{content:""\e032""}.glyphicon-lock:before{content:""\e033""}.glyphicon-flag:before{content:""\e034""}.glyphicon-headphones:before{content:""\e035""}.glyphicon-volume-off:before{content:""\e036""}.glyphicon-volume-down:before{content:""\e037""}.glyphicon-volume-up:before{content:""\e038""}.glyphicon-qrcode:before{content:""\e039""}.glyphicon-barcode:before{content:""\e040""}.glyphicon-tag:before{content:""\e041""}.glyphicon-tags:before{content:""\e042""}.glyphicon-book:before{content:""\e043""}.glyphicon-bookmark:before{content:""\e044""}.glyphicon-print:before{content:""\e045""}.glyphicon-camera:before{content:""\e046""}.glyphicon-font:before{content:""\e047""}.glyphicon-bold:before{content:""\e048""}.glyphicon-italic:before{content:""\e049""}.glyphicon-text-height:before{content:""\e050""}.glyphicon-text-width:before{content:""\e051""}.glyphicon-align-left:before{content:""\e052""}.glyphicon-align-center:before{content:""\e053""}.glyphicon-align-right:before{content:""\e054""}.glyphicon-align-justify:before{content:""\e055""}.glyphicon-list:before{content:""\e056""}.glyphicon-indent-left:before{content:""\e057""}.glyphicon-indent-right:before{content:""\e058""}.glyphicon-facetime-video:before{content:""\e059""}.glyphicon-picture:before{content:""\e060""}.glyphicon-map-marker:before{content:""\e062""}.glyphicon-adjust:before{content:""\e063""}.glyphicon-tint:before{content:""\e064""}.glyphicon-edit:before{content:""\e065""}.glyphicon-share:before{content:""\e066""}.glyphicon-check:before{content:""\e067""}.glyphicon-move:before{content:""\e068""}.glyphicon-step-backward:before{content:""\e069""}.glyphicon-fast-backward:before{content:""\e070""}.glyphicon-backward:before{content:""\e071""}.glyphicon-play:before{content:""\e072""}.glyphicon-pause:before{content:""\e073""}.glyphicon-stop:before{content:""\e074""}.glyphicon-forward:before{content:""\e075""}.glyphicon-fast-forward:before{content:""\e076""}.glyphicon-step-forward:before{content:""\e077""}.glyphicon-eject:before{content:""\e078""}.glyphicon-chevron-left:before{content:""\e079""}.glyphicon-chevron-right:before{content:""\e080""}.glyphicon-plus-sign:before{content:""\e081""}.glyphicon-minus-sign:before{content:""\e082""}.glyphicon-remove-sign:before{content:""\e083""}.glyphicon-ok-sign:before{content:""\e084""}.glyphicon-question-sign:before{content:""\e085""}.glyphicon-info-sign:before{content:""\e086""}.glyphicon-screenshot:before{content:""\e087""}.glyphicon-remove-circle:before{content:""\e088""}.glyphicon-ok-circle:before{content:""\e089""}.glyphicon-ban-circle:before{content:""\e090""}.glyphicon-arrow-left:before{content:""\e091""}.glyphicon-arrow-right:before{content:""\e092""}.glyphicon-arrow-up:before{content:""\e093""}.glyphicon-arrow-down:before{content:""\e094""}.glyphicon-share-alt:before{content:""\e095""}.glyphicon-resize-full:before{content:""\e096""}.glyphicon-resize-small:before{content:""\e097""}.glyphicon-exclamation-sign:before{content:""\e101""}.glyphicon-gift:before{content:""\e102""}.glyphicon-leaf:before{content:""\e103""}.glyphicon-fire:before{content:""\e104""}.glyphicon-eye-open:before{content:""\e105""}.glyphicon-eye-close:before{content:""\e106""}.glyphicon-warning-sign:before{content:""\e107""}.glyphicon-plane:before{content:""\e108""}.glyphicon-calendar:before{content:""\e109""}.glyphicon-random:before{content:""\e110""}.glyphicon-comment:before{content:""\e111""}.glyphicon-magnet:before{content:""\e112""}.glyphicon-chevron-up:before{content:""\e113""}.glyphicon-chevron-down:before{content:""\e114""}.glyphicon-retweet:before{content:""\e115""}.glyphicon-shopping-cart:before{content:""\e116""}.glyphicon-folder-close:before{content:""\e117""}.glyphicon-folder-open:before{content:""\e118""}.glyphicon-resize-vertical:before{content:""\e119""}.glyphicon-resize-horizontal:before{content:""\e120""}.glyphicon-hdd:before{content:""\e121""}.glyphicon-bullhorn:before{content:""\e122""}.glyphicon-bell:before{content:""\e123""}.glyphicon-certificate:before{content:""\e124""}.glyphicon-thumbs-up:before{content:""\e125""}.glyphicon-thumbs-down:before{content:""\e126""}.glyphicon-hand-right:before{content:""\e127""}.glyphicon-hand-left:before{content:""\e128""}.glyphicon-hand-up:before{content:""\e129""}.glyphicon-hand-down:before{content:""\e130""}.glyphicon-circle-arrow-right:before{content:""\e131""}.glyphicon-circle-arrow-left:before{content:""\e132""}.glyphicon-circle-arrow-up:before{content:""\e133""}.glyphicon-circle-arrow-down:before{content:""\e134""}.glyphicon-globe:before{content:""\e135""}.glyphicon-wrench:before{content:""\e136""}.glyphicon-tasks:before{content:""\e137""}.glyphicon-filter:before{content:""\e138""}.glyphicon-briefcase:before{content:""\e139""}.glyphicon-fullscreen:before{content:""\e140""}.glyphicon-dashboard:before{content:""\e141""}.glyphicon-paperclip:before{content:""\e142""}.glyphicon-heart-empty:before{content:""\e143""}.glyphicon-link:before{content:""\e144""}.glyphicon-phone:before{content:""\e145""}.glyphicon-pushpin:before{content:""\e146""}.glyphicon-usd:before{content:""\e148""}.glyphicon-gbp:before{content:""\e149""}.glyphicon-sort:before{content:""\e150""}.glyphicon-sort-by-alphabet:before{content:""\e151""}.glyphicon-sort-by-alphabet-alt:before{content:""\e152""}.glyphicon-sort-by-order:before{content:""\e153""}.glyphicon-sort-by-order-alt:before{content:""\e154""}.glyphicon-sort-by-attributes:before{content:""\e155""}.glyphicon-sort-by-attributes-alt:before{content:""\e156""}.glyphicon-unchecked:before{content:""\e157""}.glyphicon-expand:before{content:""\e158""}.glyphicon-collapse-down:before{content:""\e159""}.glyphicon-collapse-up:before{content:""\e160""}.glyphicon-log-in:before{content:""\e161""}.glyphicon-flash:before{content:""\e162""}.glyphicon-log-out:before{content:""\e163""}.glyphicon-new-window:before{content:""\e164""}.glyphicon-record:before{content:""\e165""}.glyphicon-save:before{content:""\e166""}.glyphicon-open:before{content:""\e167""}.glyphicon-saved:before{content:""\e168""}.glyphicon-import:before{content:""\e169""}.glyphicon-export:before{content:""\e170""}.glyphicon-send:before{content:""\e171""}.glyphicon-floppy-disk:before{content:""\e172""}.glyphicon-floppy-saved:before{content:""\e173""}.glyphicon-floppy-remove:before{content:""\e174""}.glyphicon-floppy-save:before{content:""\e175""}.glyphicon-floppy-open:before{content:""\e176""}.glyphicon-credit-card:before{content:""\e177""}.glyphicon-transfer:before{content:""\e178""}.glyphicon-cutlery:before{content:""\e179""}.glyphicon-header:before{content:""\e180""}.glyphicon-compressed:before{content:""\e181""}.glyphicon-earphone:before{content:""\e182""}.glyphicon-phone-alt:before{content:""\e183""}.glyphicon-tower:before{content:""\e184""}.glyphicon-stats:before{content:""\e185""}.glyphicon-sd-video:before{content:""\e186""}.glyphicon-hd-video:before{content:""\e187""}.glyphicon-subtitles:before{content:""\e188""}.glyphicon-sound-stereo:before{content:""\e189""}.glyphicon-sound-dolby:before{content:""\e190""}.glyphicon-sound-5-1:before{content:""\e191""}.glyphicon-sound-6-1:before{content:""\e192""}.glyphicon-sound-7-1:before{content:""\e193""}.glyphicon-copyright-mark:before{content:""\e194""}.glyphicon-registration-mark:before{content:""\e195""}.glyphicon-cloud-download:before{content:""\e197""}.glyphicon-cloud-upload:before{content:""\e198""}.glyphicon-tree-conifer:before{content:""\e199""}.glyphicon-tree-deciduous:before{content:""\e200""}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:""/\00a0"";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.428571429;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:gray}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group .list-group-item:first-child{border-top:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel>.list-group:first-child .list-group-item:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tfoot>tr:first-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tfoot>tr:first-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:first-child>td{border-top:0}.panel>.table-bordered>thead>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:last-child>th,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:last-child>td,.panel>.table-responsive>.table-bordered>thead>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#faebcc}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ebccd1}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.428571429px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""""}.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top .arrow:after{content:"" "";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right .arrow:after{content:"" "";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom .arrow:after{content:"" "";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left .arrow:after{content:"" "";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5) 0),color-stop(rgba(0,0,0,.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.0001) 0),color-stop(rgba(0,0,0,.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicons-chevron-left,.carousel-control .glyphicons-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:"" "";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,tr.visible-xs,th.visible-xs,td.visible-xs{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}.visible-sm,tr.visible-sm,th.visible-sm,td.visible-sm{display:none!important}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}.visible-md,tr.visible-md,th.visible-md,td.visible-md{display:none!important}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}.visible-lg,tr.visible-lg,th.visible-lg,td.visible-lg{display:none!important}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (max-width:767px){.hidden-xs,tr.hidden-xs,th.hidden-xs,td.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm,tr.hidden-sm,th.hidden-sm,td.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md,tr.hidden-md,th.hidden-md,td.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg,tr.hidden-lg,th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print,tr.visible-print,th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}@media print{.hidden-print,tr.hidden-print,th.hidden-print,td.hidden-print{display:none!important}} + + caption { text-align: left; } + h3 { margin-top: 3em; } + .no-default { font-style: italic; } + .return-type { font-weight: bold; } + .schema-objects-list-container { width: 40%; min-width: 200px } + th { background-color: #B0C4DE; } + body {margin-left: .5em;} + "; + + + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Document/DocumentGeneratorConfiguration.cs b/SqlServerDatabaseDocumentationGenerator/Document/DocumentGeneratorConfiguration.cs new file mode 100644 index 0000000..7c5260b --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Document/DocumentGeneratorConfiguration.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Document +{ + /// + /// Configuration options for document generation + /// + public class DocumentGeneratorConfiguration + { + public bool ForeignKeyToTableHyperLink { get; set; } + + public bool CheckForDesignIssues { get; set; } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Document/FormattingExtensionMethod.cs b/SqlServerDatabaseDocumentationGenerator/Document/FormattingExtensionMethod.cs new file mode 100644 index 0000000..a92468c --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Document/FormattingExtensionMethod.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Document +{ + public static class FormattingExtensionMethod + { + /// + /// Get the text for a unique ID for Anchoring the object within the document + /// + /// + /// + public static string GetObjectAnchorId(this IDbObject targetObj) + { + string result = null; + + if (targetObj != null) + { + result = String.Concat("_", targetObj.ObjectId); + } + + return result ?? String.Empty; + + } + + /// + /// Get the target text for a foreign key by anchor ID + /// + /// + /// + public static string GetFkTargetAnchorId(this ForeignKey fk) + { + string result = null; + + if (fk != null) + { + result = String.Concat("#_", fk.ReferencedObjectId); + } + + return result ?? String.Empty; + } + + + public static string ToYesNo(this bool input) + { + if (input) + { + return "Yes"; + } + + return "No"; + } + + + /// + /// For a given foreign key get the column names as array of string + /// + /// + /// + public static string[] GetForeignKeyParentColumnNames(this ForeignKey fk) + { + return ( + from fkc in fk.ForeignKeyColumns + orderby fkc.ConstraintColumnId + select fkc.ParentColumnName + ).ToArray(); + + } + + public static string[] GetForeignKeyReferenceColumnNames(this ForeignKey fk) + { + return ( + from fkc in fk.ForeignKeyColumns + orderby fkc.ConstraintColumnId + select fkc.ReferenceColumnName + ).ToArray(); + } + + /// + /// Create reader friendly text for display of a functon return value + /// + /// + /// + public static string GetReturnTypeDisplayText(this ScalarFunction func) + { + var dispText = new StringBuilder(); + + if (!String.IsNullOrWhiteSpace(func.ReturnDataType)) + { + dispText.Append(func.ReturnDataType); + } + + //has numeric precision and scale + if (func.ReturnTypePrecision.HasValue && func.ReturnTypeScale.HasValue) + { + dispText.Append(String.Format("({0},{1})", func.ReturnTypePrecision.Value, func.ReturnTypeScale.Value)); + } + else + { + if (func.ReturnTypeMaximumLength.HasValue && func.ReturnTypeMaximumLength != -1) + { + dispText.Append(String.Format("({0})", func.ReturnTypeMaximumLength.Value)); + } + } + + return dispText.ToString(); + + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/ColumnInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/ColumnInspector.cs new file mode 100644 index 0000000..0608b3b --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/ColumnInspector.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using PetaPoco; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class ColumnInspector: CommonInspector + { + + + public ColumnInspector(PetaPoco.Database petaDb): base(petaDb) + { + + } + + + public IList GetColumns(IDbObject parent) + { + IList result = null; + + if (parent is Table) + { + result = this.queryForTableColumns(parent as Table); + } + + if (parent is View) + { + result = this.queryForViewColumns(parent as View); + } + + if (parent is TableFunction) + { + result = this.queryForFunctionColumns(parent as TableFunction); + } + + if (result != null && result.Count > 0) + { + for (int i = 0; i < result.Count; i++) + { + result[i].Parent = parent; + } + } + + return result; + } + + private IList queryForViewColumns(View view) + { + var sql = new Sql(@"SELECT C.name AS ColumnName + , I.DATA_TYPE AS BaseDataTypeName + , I.CHARACTER_MAXIMUM_LENGTH AS MaximumLength + , C.is_nullable AS AllowNull + , CONVERT(INT, I.NUMERIC_PRECISION) AS Precision + , CONVERT(INT, I.NUMERIC_SCALE) AS Scale + , C.object_id AS ColumnId + , I.COLUMN_DEFAULT AS DefaultValue + , C.is_identity AS IsIdentity + , C.is_computed AS IsComputed + , Y.name AS TypeName + , Y.is_user_defined AS IsUserDefinedType + + FROM sys.views AS V + INNER JOIN sys.schemas AS S + ON ( V.schema_id = S.schema_id ) + INNER JOIN sys.columns AS C + ON ( V.object_id = C.object_id ) + INNER JOIN sys.types AS Y + ON ( Y.user_type_id = C.user_type_id ) + INNER JOIN INFORMATION_SCHEMA.COLUMNS AS I + ON ( I.TABLE_SCHEMA = S.[name] AND I.TABLE_NAME = V.[name] AND I.COLUMN_NAME = C.[name] ) + + WHERE V.object_id = @0 + + ORDER BY C.column_id, C.name;", view.ViewId); + + + return this.peta.Fetch(sql); + } + + + private List queryForTableColumns(Table parentTable) + { + + + //petapoco's automapping columns has issues with smallint so we'll explicity convert to int in query + var sql = new Sql(@"SELECT C.name AS ColumnName + , EP.value AS [Description] + , I.DATA_TYPE AS BaseDataTypeName + , I.CHARACTER_MAXIMUM_LENGTH AS MaximumLength + , C.is_nullable AS AllowNull + , CONVERT(INT, I.NUMERIC_PRECISION) AS Precision + , CONVERT(INT, I.NUMERIC_SCALE) AS Scale + , C.object_id AS ColumnId + , I.COLUMN_DEFAULT AS DefaultValue + , C.is_identity AS IsIdentity + , C.is_computed AS IsComputed + , Y.name AS TypeName + , Y.is_user_defined AS IsUserDefinedType + + FROM sys.tables AS T + INNER JOIN sys.schemas AS S + ON ( T.schema_id = S.schema_id ) + INNER JOIN sys.columns AS C + ON ( T.object_id = C.object_id ) + INNER JOIN sys.types AS Y + ON ( Y.user_type_id = C.user_type_id ) + INNER JOIN INFORMATION_SCHEMA.COLUMNS AS I + ON ( I.TABLE_SCHEMA = S.[name] AND I.TABLE_NAME = T.[name] AND I.COLUMN_NAME = C.[name] ) + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 1 AND EP.major_id = C.object_id AND EP.minor_id = C.column_id AND EP.name = 'MS_Description' ) + + WHERE T.object_id = @0 + + ORDER BY C.column_id, C.name;", parentTable.TableId); + + return this.peta.Fetch(sql); + } + + + private IList queryForFunctionColumns(TableFunction func) + { + var sql = new Sql(@"SELECT + C.name AS ColumnName + , Y.[name] AS BaseDataTypeName + , C.is_nullable AS AllowNull + + FROM sys.objects AS T + INNER JOIN sys.schemas AS S + ON ( T.schema_id = S.schema_id ) + INNER JOIN sys.columns AS C + ON ( T.object_id = C.object_id ) + INNER JOIN sys.types AS Y + ON ( Y.user_type_id = C.user_type_id ) + + WHERE T.object_id = @0 + AND T.type = 'TF' + + ORDER BY C.column_id, C.name;", func.FunctionId); + + return this.peta.Fetch(sql); + + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/CommonInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/CommonInspector.cs new file mode 100644 index 0000000..ca68ecd --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/CommonInspector.cs @@ -0,0 +1,23 @@ +using System; +using PetaPoco; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + /// + /// Base class for inspectors + /// + public abstract class CommonInspector + { + protected Database peta; + + public CommonInspector(Database petaDb) + { + this.peta = petaDb; + } + + public CommonInspector() + { + //if petapoco database object not passed in constructor object should build one + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/DatabaseInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/DatabaseInspector.cs new file mode 100644 index 0000000..64df302 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/DatabaseInspector.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Data.SqlClient; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class DatabaseInspector : CommonInspector + { + + private SqlConnection sqlConnection; + + private string sqlConnectionString; + + + public DatabaseInspector(string connstring) + { + + this.sqlConnectionString = connstring; + this.sqlConnection = new SqlConnection(this.sqlConnectionString); + } + + + + public Database GetDatabaseMetaData() + { + Database database = null; + + if (SqlConnectionTester.TestConnectionString(this.sqlConnectionString, false).Success) + { + this.peta = new PetaPoco.Database(this.sqlConnectionString, "System.Data.SqlClient"); + + + database = this.queryForDatabase(); + + var schemaInspector = new SchemaInspector(this.peta); + + database.Schemas = schemaInspector.GetSchemas(database); + + //TODO: add input parameter to control inspection for design issues + DesignIssue.DatabaseDesignIssueInspector designIssueInspector = new DesignIssue.DatabaseDesignIssueInspector(); + database.DesignIssueWarnings = designIssueInspector.GetDesignIssueWarnings(database); + + + } + + return database; + } + + private Database queryForDatabase() + { + var sql = new PetaPoco.Sql(@"SELECT DatabaseName + , ObjectId + , EP.value AS [Description] + FROM + ( SELECT DB_NAME() AS DatabaseName, DB_ID() AS ObjectId ) AS DbInfo + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 0 AND EP.[name] = 'MS_Description' );"); + + return this.peta.FirstOrDefault(sql); + } + + /* + /// + /// Lookup name of current database + /// + /// + private string getDatabaseName() + { + var sql = new PetaPoco.Sql("SELECT DB_NAME();"); + return this.peta.ExecuteScalar(sql); + } + + + private int getDatabaseId() + { + var sql = new PetaPoco.Sql("SELECT DB_ID();"); + return this.peta.ExecuteScalar(sql); + } + + + */ + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/DatabaseDesignIssueInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/DatabaseDesignIssueInspector.cs new file mode 100644 index 0000000..1855e07 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/DatabaseDesignIssueInspector.cs @@ -0,0 +1,884 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection.DesignIssue +{ + /// + /// Examines database for potential design issues + /// + /// + /// Design issues include things like + /// Naming stored procedures with 'sp_*' + /// Naming objects using SQL server reserved keywords + /// + public class DatabaseDesignIssueInspector + { + + private StoredProcedureDesignIssueInspector sprocInspector = new StoredProcedureDesignIssueInspector(); + + /// + /// Reserve words in SQL Server + /// + /// + /// See: https://msdn.microsoft.com/en-us/library/ms189822.aspx + /// + private readonly string[] reservedWords = new string[] { + "ADD", + "EXISTS", + "PRECISION", + "ALL", + "EXIT", + "PRIMARY", + "ALTER", + "EXTERNAL", + "PRINT", + "AND", + "FETCH", + "PROC", + "ANY", + "FILE", + "PROCEDURE", + "AS", + "FILLFACTOR", + "PUBLIC", + "ASC", + "FOR", + "RAISERROR", + "AUTHORIZATION", + "FOREIGN", + "READ", + "BACKUP", + "FREETEXT", + "READTEXT", + "BEGIN", + "FREETEXTTABLE", + "RECONFIGURE", + "BETWEEN", + "FROM", + "REFERENCES", + "BREAK", + "FULL", + "REPLICATION", + "BROWSE", + "FUNCTION", + "RESTORE", + "BULK", + "GOTO", + "RESTRICT", + "BY", + "GRANT", + "RETURN", + "CASCADE", + "GROUP", + "REVERT", + "CASE", + "HAVING", + "REVOKE", + "CHECK", + "HOLDLOCK", + "RIGHT", + "CHECKPOINT", + "IDENTITY", + "ROLLBACK", + "CLOSE", + "IDENTITY_INSERT", + "ROWCOUNT", + "CLUSTERED", + "IDENTITYCOL", + "ROWGUIDCOL", + "COALESCE", + "IF", + "RULE", + "COLLATE", + "IN", + "SAVE", + "COLUMN", + "INDEX", + "SCHEMA", + "COMMIT", + "INNER", + "SECURITYAUDIT", + "COMPUTE", + "INSERT", + "SELECT", + "CONSTRAINT", + "INTERSECT", + "SESSION_USER", + "CONTAINS", + "INTO", + "SET", + "CONTAINSTABLE", + "IS", + "SETUSER", + "CONTINUE", + "JOIN", + "SHUTDOWN", + "CONVERT", + "KEY", + "SOME", + "CREATE", + "KILL", + "STATISTICS", + "CROSS", + "LEFT", + "SYSTEM_USER", + "CURRENT", + "LIKE", + "TABLE", + "CURRENT_DATE", + "LINENO", + "TABLESAMPLE", + "CURRENT_TIME", + "LOAD", + "TEXTSIZE", + "CURRENT_TIMESTAMP", + "MERGE", + "THEN", + "CURRENT_USER", + "NATIONAL", + "TO", + "CURSOR", + "NOCHECK", + "TOP", + "DATABASE", + "NONCLUSTERED", + "TRAN", + "DBCC", + "NOT", + "TRANSACTION", + "DEALLOCATE", + "NULL", + "TRIGGER", + "DECLARE", + "NULLIF", + "TRUNCATE", + "DEFAULT", + "OF", + "TSEQUAL", + "DELETE", + "OFF", + "UNION", + "DENY", + "OFFSETS", + "UNIQUE", + "DESC", + "ON", + "UNPIVOT", + "DISK", + "OPEN", + "UPDATE", + "DISTINCT", + "OPENDATASOURCE", + "UPDATETEXT", + "DISTRIBUTED", + "OPENQUERY", + "USE", + "DOUBLE", + "OPENROWSET", + "USER", + "DROP", + "OPENXML", + "VALUES", + "DUMP", + "OPTION", + "VARYING", + "ELSE", + "OR", + "VIEW", + "END", + "ORDER", + "WAITFOR", + "ERRLVL", + "OUTER", + "WHEN", + "ESCAPE", + "OVER", + "WHERE", + "EXCEPT", + "PERCENT", + "WHILE", + "EXEC", + "PIVOT", + "WITH", + "EXECUTE", + "PLAN", + "WRITETEXT", + "ABSOLUTE", + "EXEC", + "OVERLAPS", + "ACTION", + "EXECUTE", + "PAD", + "ADA", + "EXISTS", + "PARTIAL", + "ADD", + "EXTERNAL", + "PASCAL", + "ALL", + "EXTRACT", + "POSITION", + "ALLOCATE", + "FALSE", + "PRECISION", + "ALTER", + "FETCH", + "PREPARE", + "AND", + "FIRST", + "PRESERVE", + "ANY", + "FLOAT", + "PRIMARY", + "ARE", + "FOR", + "PRIOR", + "AS", + "FOREIGN", + "PRIVILEGES", + "ASC", + "FORTRAN", + "PROCEDURE", + "ASSERTION", + "FOUND", + "PUBLIC", + "AT", + "FROM", + "READ", + "AUTHORIZATION", + "FULL", + "REAL", + "AVG", + "GET", + "REFERENCES", + "BEGIN", + "GLOBAL", + "RELATIVE", + "BETWEEN", + "GO", + "RESTRICT", + "BIT", + "GOTO", + "REVOKE", + "BIT_LENGTH", + "GRANT", + "RIGHT", + "BOTH", + "GROUP", + "ROLLBACK", + "BY", + "HAVING", + "ROWS", + "CASCADE", + "HOUR", + "SCHEMA", + "CASCADED", + "IDENTITY", + "SCROLL", + "CASE", + "IMMEDIATE", + "SECOND", + "CAST", + "IN", + "SECTION", + "CATALOG", + "INCLUDE", + "SELECT", + "CHAR", + "INDEX", + "SESSION", + "CHAR_LENGTH", + "INDICATOR", + "SESSION_USER", + "CHARACTER", + "INITIALLY", + "SET", + "CHARACTER_LENGTH", + "INNER", + "SIZE", + "CHECK", + "INPUT", + "SMALLINT", + "CLOSE", + "INSENSITIVE", + "SOME", + "COALESCE", + "INSERT", + "SPACE", + "COLLATE", + "INT", + "SQL", + "COLLATION", + "INTEGER", + "SQLCA", + "COLUMN", + "INTERSECT", + "SQLCODE", + "COMMIT", + "INTERVAL", + "SQLERROR", + "CONNECT", + "INTO", + "SQLSTATE", + "CONNECTION", + "IS", + "SQLWARNING", + "CONSTRAINT", + "ISOLATION", + "SUBSTRING", + "CONSTRAINTS", + "JOIN", + "SUM", + "CONTINUE", + "KEY", + "SYSTEM_USER", + "CONVERT", + "LANGUAGE", + "TABLE", + "CORRESPONDING", + "LAST", + "TEMPORARY", + "COUNT", + "LEADING", + "THEN", + "CREATE", + "LEFT", + "TIME", + "CROSS", + "LEVEL", + "TIMESTAMP", + "CURRENT", + "LIKE", + "TIMEZONE_HOUR", + "CURRENT_DATE", + "LOCAL", + "TIMEZONE_MINUTE", + "CURRENT_TIME", + "LOWER", + "TO", + "CURRENT_TIMESTAMP", + "MATCH", + "TRAILING", + "CURRENT_USER", + "MAX", + "TRANSACTION", + "CURSOR", + "MIN", + "TRANSLATE", + "DATE", + "MINUTE", + "TRANSLATION", + "DAY", + "MODULE", + "TRIM", + "DEALLOCATE", + "MONTH", + "TRUE", + "DEC", + "NAMES", + "UNION", + "DECIMAL", + "NATIONAL", + "UNIQUE", + "DECLARE", + "NATURAL", + "UNKNOWN", + "DEFAULT", + "NCHAR", + "UPDATE", + "DEFERRABLE", + "NEXT", + "UPPER", + "DEFERRED", + "NO", + "USAGE", + "DELETE", + "NONE", + "USER", + "DESC", + "NOT", + "USING", + "DESCRIBE", + "NULL", + "VALUE", + "DESCRIPTOR", + "NULLIF", + "VALUES", + "DIAGNOSTICS", + "NUMERIC", + "VARCHAR", + "DISCONNECT", + "OCTET_LENGTH", + "VARYING", + "DISTINCT", + "OF", + "VIEW", + "DOMAIN", + "ON", + "WHEN", + "DOUBLE", + "ONLY", + "WHENEVER", + "DROP", + "OPEN", + "WHERE", + "ELSE", + "OPTION", + "WITH", + "END", + "OR", + "WORK", + "END-EXEC", + "ORDER", + "WRITE", + "ESCAPE", + "OUTER", + "YEAR", + "EXCEPT", + "OUTPUT", + "ZONE", + "EXCEPTION", + "ABSOLUTE", + "HOST", + "RELATIVE", + "ACTION", + "HOUR", + "RELEASE", + "ADMIN", + "IGNORE", + "RESULT", + "AFTER", + "IMMEDIATE", + "RETURNS", + "AGGREGATE", + "INDICATOR", + "ROLE", + "ALIAS", + "INITIALIZE", + "ROLLUP", + "ALLOCATE", + "INITIALLY", + "ROUTINE", + "ARE", + "INOUT", + "ROW", + "ARRAY", + "INPUT", + "ROWS", + "ASENSITIVE", + "INT", + "SAVEPOINT", + "ASSERTION", + "INTEGER", + "SCROLL", + "ASYMMETRIC", + "INTERSECTION", + "SCOPE", + "AT", + "INTERVAL", + "SEARCH", + "ATOMIC", + "ISOLATION", + "SECOND", + "BEFORE", + "ITERATE", + "SECTION", + "BINARY", + "LANGUAGE", + "SENSITIVE", + "BIT", + "LARGE", + "SEQUENCE", + "BLOB", + "LAST", + "SESSION", + "BOOLEAN", + "LATERAL", + "SETS", + "BOTH", + "LEADING", + "SIMILAR", + "BREADTH", + "LESS", + "SIZE", + "CALL", + "LEVEL", + "SMALLINT", + "CALLED", + "LIKE_REGEX", + "SPACE", + "CARDINALITY", + "LIMIT", + "SPECIFIC", + "CASCADED", + "LN", + "SPECIFICTYPE", + "CAST", + "LOCAL", + "SQL", + "CATALOG", + "LOCALTIME", + "SQLEXCEPTION", + "CHAR", + "LOCALTIMESTAMP", + "SQLSTATE", + "CHARACTER", + "LOCATOR", + "SQLWARNING", + "CLASS", + "MAP", + "START", + "CLOB", + "MATCH", + "STATE", + "COLLATION", + "MEMBER", + "STATEMENT", + "COLLECT", + "METHOD", + "STATIC", + "COMPLETION", + "MINUTE", + "STDDEV_POP", + "CONDITION", + "MOD", + "STDDEV_SAMP", + "CONNECT", + "MODIFIES", + "STRUCTURE", + "CONNECTION", + "MODIFY", + "SUBMULTISET", + "CONSTRAINTS", + "MODULE", + "SUBSTRING_REGEX", + "CONSTRUCTOR", + "MONTH", + "SYMMETRIC", + "CORR", + "MULTISET", + "SYSTEM", + "CORRESPONDING", + "NAMES", + "TEMPORARY", + "COVAR_POP", + "NATURAL", + "TERMINATE", + "COVAR_SAMP", + "NCHAR", + "THAN", + "CUBE", + "NCLOB", + "TIME", + "CUME_DIST", + "NEW", + "TIMESTAMP", + "CURRENT_CATALOG", + "NEXT", + "TIMEZONE_HOUR", + "CURRENT_DEFAULT_TRANSFORM_GROUP", + "NO", + "TIMEZONE_MINUTE", + "CURRENT_PATH", + "NONE", + "TRAILING", + "CURRENT_ROLE", + "NORMALIZE", + "TRANSLATE_REGEX", + "CURRENT_SCHEMA", + "NUMERIC", + "TRANSLATION", + "CURRENT_TRANSFORM_GROUP_FOR_TYPE", + "OBJECT", + "TREAT", + "CYCLE", + "OCCURRENCES_REGEX", + "TRUE", + "DATA", + "OLD", + "UESCAPE", + "DATE", + "ONLY", + "UNDER", + "DAY", + "OPERATION", + "UNKNOWN", + "DEC", + "ORDINALITY", + "UNNEST", + "DECIMAL", + "OUT", + "USAGE", + "DEFERRABLE", + "OVERLAY", + "USING", + "DEFERRED", + "OUTPUT", + "VALUE", + "DEPTH", + "PAD", + "VAR_POP", + "DEREF", + "PARAMETER", + "VAR_SAMP", + "DESCRIBE", + "PARAMETERS", + "VARCHAR", + "DESCRIPTOR", + "PARTIAL", + "VARIABLE", + "DESTROY", + "PARTITION", + "WHENEVER", + "DESTRUCTOR", + "PATH", + "WIDTH_BUCKET", + "DETERMINISTIC", + "POSTFIX", + "WITHOUT", + "DICTIONARY", + "PREFIX", + "WINDOW", + "DIAGNOSTICS", + "PREORDER", + "WITHIN", + "DISCONNECT", + "PREPARE", + "WORK", + "DOMAIN", + "PERCENT_RANK", + "WRITE", + "DYNAMIC", + "PERCENTILE_CONT", + "XMLAGG", + "EACH", + "PERCENTILE_DISC", + "XMLATTRIBUTES", + "ELEMENT", + "POSITION_REGEX", + "XMLBINARY", + "END-EXEC", + "PRESERVE", + "XMLCAST", + "EQUALS", + "PRIOR", + "XMLCOMMENT", + "EVERY", + "PRIVILEGES", + "XMLCONCAT", + "EXCEPTION", + "RANGE", + "XMLDOCUMENT", + "FALSE", + "READS", + "XMLELEMENT", + "FILTER", + "REAL", + "XMLEXISTS", + "FIRST", + "RECURSIVE", + "XMLFOREST", + "FLOAT", + "REF", + "XMLITERATE", + "FOUND", + "REFERENCING", + "XMLNAMESPACES", + "FREE", + "REGR_AVGX", + "XMLPARSE", + "FULLTEXTTABLE", + "REGR_AVGY", + "XMLPI", + "FUSION", + "REGR_COUNT", + "XMLQUERY", + "GENERAL", + "REGR_INTERCEPT", + "XMLSERIALIZE", + "GET", + "REGR_R2", + "XMLTABLE", + "GLOBAL", + "REGR_SLOPE", + "XMLTEXT", + "GO", + "REGR_SXX", + "XMLVALIDATE", + "GROUPING", + "REGR_SXY", + "YEAR", + "HOLD", + "REGR_SYY" + }; + + /// + /// Special characters to avoid in object names + /// + /// + /// See: https://msdn.microsoft.com/en-us/library/dd172134(v=vs.100).aspx + /// + private readonly char[] specialCharacters = new char[] { }; + + private DesignIssueWarning getDesignIssueForObjectsWithSpecialCharactersInName(Model.Database database) + { + DesignIssueWarning warning = new DesignIssueWarning() + { + Description = "Database object names should not contain special characters", + ReferenceUrl = new Uri("https://msdn.microsoft.com/en-us/library/dd172134(v=vs.100).aspx") + }; + + + if (database == null) + { + return null; //cannot act on empty object + } + + List objectList = new List(); + + // check Db name + if (this.checkForSpecialCharacters(database.ObjectName)) + { + objectList.Add(database); + } + + + IList allDbObjList = database.GetAllObjects(); + + foreach (IDbObject obj in allDbObjList) + { + if (this.checkForSpecialCharacters(obj.ObjectName)) + { + objectList.Add(obj); + } + } + + + //do we have any objects? + if (objectList.HasAny()) + { + warning.DatabaseObjects = objectList; + } + else + { + warning = null; + } + + + return warning; + } + + private bool checkForSpecialCharacters(string input) + { + + if (input == null) + { + throw new ArgumentNullException("input"); + } + + + bool containSpecial = false; + + //match for: whitepace, double quote, single quote, square brackets + Regex re = new Regex(@"[\s\""\'\[\]]+", RegexOptions.Compiled); + + Match m = re.Match(input); + containSpecial = m.Success; + + return containSpecial; + } + + + + private DesignIssueWarning getDesignIssueWarningForObjectNamedWithReservedWordssignIssueWarning(Model.Database database) + { + DesignIssueWarning warning = new DesignIssueWarning() + { + Description = "Database object names should not be reserved words", + ReferenceUrl = new Uri("https://msdn.microsoft.com/en-us/library/ms189822(SQL.100).aspx") + }; + + if (database == null) + { + return null; //cannot act on empty object + } + + List objectList = new List(); + + if (this.reservedWords.Contains(database.ObjectName, StringComparer.OrdinalIgnoreCase)) + { + objectList.Add(database as IDbObject); + } + + IList allDbObjList = database.GetAllObjects(); + + + // union objects to one collection to get one list to parse + var nameViolations = ( + from obj in allDbObjList + where this.reservedWords.Contains(obj.ObjectName, StringComparer.OrdinalIgnoreCase) + orderby obj.ObjectFullDisplayName + select obj + ).ToList(); + + objectList.AddRange(nameViolations); + + + //do we have any objects? + if (objectList.HasAny()) + { + warning.DatabaseObjects = objectList; + } + else + { + warning = null; + } + + + return warning; + + } + + public List GetDesignIssueWarnings(Model.Database database) + { + + + if (database == null) + { + return null; //cannot act on empty input + } + + List warningList = new List(); + + DesignIssueWarning objectNamedWithReservedWord = this.getDesignIssueWarningForObjectNamedWithReservedWordssignIssueWarning(database); + if (objectNamedWithReservedWord != null) + { + warningList.Add(objectNamedWithReservedWord); + } + + DesignIssueWarning objectNameContainingSpecialChars = this.getDesignIssueForObjectsWithSpecialCharactersInName(database); + if (objectNameContainingSpecialChars != null) + { + warningList.Add(objectNameContainingSpecialChars); + } + + + List sprocWarningList = this.sprocInspector.GetDesignIssueWarning(database); + if (sprocWarningList.HasAny()) + { + warningList.AddRange(sprocWarningList); + } + + + + return warningList; + + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/StoredProcedureDesignIssueInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/StoredProcedureDesignIssueInspector.cs new file mode 100644 index 0000000..c7064ba --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/DesignIssue/StoredProcedureDesignIssueInspector.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection.DesignIssue +{ + public class StoredProcedureDesignIssueInspector + { + + + + public List GetDesignIssueWarning(Model.Database database) + { + List warningList = new List(); + + if (database == null || !database.Schemas.HasAny()) + { + return null; //cannot act on empty object + } + + this.checkForNameStartingWithSpUndercoreWarning(database, ref warningList); + + + //TODO: finish + + return warningList; + + } + + /// + /// Handles checking for stored procedures with name starting with 'sp_; + /// + /// Database to examine + /// List to add to if issues found + private void checkForNameStartingWithSpUndercoreWarning(Database database, ref List warningList) + { + if (database == null || !database.Schemas.HasAny()) + { + return; + } + + var problems = ( + from schema in database.Schemas + from sproc in schema.StoredProcedures + where sproc.ObjectName.StartsWith("sp_", StringComparison.OrdinalIgnoreCase) + select sproc as IDbObject + ).ToList(); + + if (problems.HasAny()) + { + DesignIssueWarning sprocNameWarning = new DesignIssueWarning() + { + Description = "Stored Procedures with name staring with 'sp_' are not recommended", + ReferenceUrl = new Uri("https://msdn.microsoft.com/en-us/library/ms190669(v=sql.105).aspx"), + DatabaseObjects = problems + }; + + warningList.Add(sprocNameWarning); + + + } + } + + + + + + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/ForeignKeyInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/ForeignKeyInspector.cs new file mode 100644 index 0000000..e9db630 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/ForeignKeyInspector.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using PetaPoco; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class ForeignKeyInspector : CommonInspector + { + + public ForeignKeyInspector(PetaPoco.Database petaDb):base(petaDb) + { + + } + + public IList GetForeignKeys(Table table) + { + IList fkList = this.queryForForeignKeys(table); + + if (fkList != null && fkList.Count > 0) + { + for (int k = 0; k < fkList.Count; k++) + { + var fk = fkList[k]; + + fk.ForeignKeyColumns = this.queryForForeignKeyColumns(table, fk); + fk.Parent = table; + } + + + } + + + return fkList; + } + + private IList queryForForeignKeyColumns(Table table, ForeignKey fk) + { + var sql = new Sql(@"SELECT + FKC.parent_column_id AS ParentColumnId + , PC.[name] AS ParentColumnName + , FKC.referenced_column_id AS ReferenceColumnId + , RC.[name] AS ReferenceColumnName + , FKC.constraint_column_id AS ConstraintColumnId + + FROM sys.foreign_key_columns AS FKC + INNER JOIN sys.columns AS PC + ON ( FKC.parent_object_id = PC.object_id AND FKC.parent_column_id = PC.column_id ) + INNER JOIN sys.columns AS RC + ON ( FKC.referenced_object_id = RC.object_id AND FKC.referenced_column_id = RC.column_id ) + + WHERE FKC.parent_object_id = @0 + AND FKC.constraint_object_id = @1 + + ORDER BY FKC.constraint_column_id;", table.TableId, fk.ForeignKeyId); + + return this.peta.Fetch(sql); + } + + private IList queryForForeignKeys(Table table) + { + var sql = new Sql(@"SELECT + FK.[name] AS ForeignKeyName + , FK.object_id AS ForeignKeyId + , FK.referenced_object_id AS ReferencedObjectId + , REF.[name] AS ReferencedObjectName + , RS.[name] AS ReferencedObjectSchemaName + , FK.delete_referential_action_desc AS DeleteReferentialAction + , FK.update_referential_action_desc AS UpdateReferentialAction + , FK.is_disabled AS IsDisabled + , FK.is_not_trusted AS IsNotTrusted + , EP.value AS [Description] + + FROM sys.foreign_keys AS FK + INNER JOIN sys.objects AS REF + ON ( FK.referenced_object_id = REF.object_id ) + INNER JOIN sys.schemas AS RS + ON ( REF.schema_id = RS.schema_id ) + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 1 AND EP.name = 'MS_Description' AND EP.major_id = FK.object_id AND EP.minor_id = 0 ) + + WHERE FK.parent_object_id = @0 + + ORDER BY FK.[name];", table.TableId); + + return this.peta.Fetch(sql); + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/IndexInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/IndexInspector.cs new file mode 100644 index 0000000..5ef3ee5 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/IndexInspector.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using PetaPoco; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class IndexInspector : CommonInspector + { + + + public IndexInspector(PetaPoco.Database petaDb):base(petaDb) + { + + } + + public IList GetIndexes(IDbObject parent) + { + IList indexList = null; + + indexList = this.getTableOrViewIndexes(parent); + + //lookup columns + if (indexList != null && indexList.Count > 0) + { + for (int i = 0; i < indexList.Count; i++) + { + indexList[i].ColumnNames = this.getIndexColumnNames(indexList[i], parent); + indexList[i].Parent = parent; + } + + } + + return indexList; + } + + private IList getIndexColumnNames(Index index, IDbObject parent) + { + var sql = new Sql(@"SELECT C.[name] + + FROM sys.indexes AS I + INNER JOIN sys.index_columns AS IC + ON ( I.index_id = IC.index_id AND I.object_id = IC.object_id ) + INNER JOIN sys.columns AS C + ON ( C.object_id = IC.object_id AND C.column_id = IC.column_id ) + + WHERE I.object_id = @0 + AND I.index_id = @1 + + ORDER BY IC.key_ordinal, C.[name];", parent.ObjectId, index.IndexId); + + return this.peta.Fetch(sql); + + } + + + private IList getTableOrViewIndexes(IDbObject parent) + { + + var sql = new Sql(@"SELECT I.[name] AS IndexName + , I.index_id AS IndexId + , EP.value AS [Description] + , I.is_unique AS IsUnique + , I.is_primary_key AS IsPrimaryKey + , I.type_desc AS IndexTypeDescription + + FROM sys.indexes AS I + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 7 AND EP.name = 'MS_Description' AND EP.major_id = I.object_id AND EP.minor_id = I.index_id ) + + WHERE object_id = @0 + AND I.index_id != 0 + + ORDER BY I.[name];", parent.ObjectId); + + return this.peta.Fetch(sql); + } + + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/ParameterInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/ParameterInspector.cs new file mode 100644 index 0000000..1be8dc7 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/ParameterInspector.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PetaPoco; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class ParameterInspector: CommonInspector + { + + public ParameterInspector(PetaPoco.Database petaDb) : base(petaDb) + { + + } + + public IList GetParameters(IDbRoutine routine) + { + + IList results = null; + + if (routine is StoredProcedure) + { + results = this.queryForStoredProcedureParameters(routine as StoredProcedure); + } + else + { + if (routine is IUserDefinedFunction) + { + + results = this.queryForUserDefinedFunctionParameters(routine as IUserDefinedFunction); + } + + } + + + if (results != null && results.Count > 0 && routine is IDbObject) + { + for (int i = 0; i < results.Count; i++) + { + results[i].Parent = routine as IDbObject; + } + } + + return results; + } + + private IList queryForUserDefinedFunctionParameters(IUserDefinedFunction func) + { + var sql = new Sql(@"SELECT + PM.name AS ParameterName + , IP.CHARACTER_MAXIMUM_LENGTH AS [MaximumLength] + , CONVERT(INT, IP.NUMERIC_PRECISION) AS Precision + , CONVERT(INT, IP.NUMERIC_SCALE) AS Scale + , IP.DATA_TYPE AS DataType + , EP.value AS [Description] + + FROM sys.objects AS J + INNER JOIN sys.parameters AS PM + ON ( J.object_id = PM.object_id ) + INNER JOIN sys.schemas AS S + ON ( J.schema_id = S.schema_id ) + INNER JOIN INFORMATION_SCHEMA.PARAMETERS AS IP + ON ( IP.SPECIFIC_SCHEMA = S.[name] AND IP.SPECIFIC_NAME = J.[name] AND IP.PARAMETER_NAME = PM.[name] ) + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 2 AND EP.[name] = 'MS_Description' AND EP.major_id = J.object_id AND EP.minor_id = PM.parameter_id ) + + WHERE J.object_id = @0 + AND IP.IS_RESULT = 'NO' + AND J.[type] IN ( 'FN', 'TF' ) + + ORDER BY IP.ORDINAL_POSITION, IP.PARAMETER_NAME;", func.FunctionId); + + return this.peta.Fetch(sql); + } + + private IList queryForStoredProcedureParameters(StoredProcedure proc) + { + + var sql = new Sql(@"SELECT + PM.name AS ParameterName + , IP.CHARACTER_MAXIMUM_LENGTH AS [MaximumLength] + , CONVERT(INT, IP.NUMERIC_PRECISION) AS Precision + , CONVERT(INT, IP.NUMERIC_SCALE) AS Scale + , IP.DATA_TYPE AS DataType + , CASE + WHEN PM.is_output = 1 + THEN 'Output' + ELSE 'Input' + END AS Direction + , EP.value AS [Description] + + FROM sys.procedures AS SP + INNER JOIN sys.parameters AS PM + ON ( SP.object_id = PM.object_id ) + INNER JOIN sys.schemas AS S + ON ( SP.schema_id = S.schema_id ) + INNER JOIN INFORMATION_SCHEMA.PARAMETERS AS IP + ON ( IP.SPECIFIC_SCHEMA = S.[name] AND IP.SPECIFIC_NAME = SP.[name] AND IP.PARAMETER_NAME = PM.[name] ) + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 2 AND EP.[name] = 'MS_Description' AND EP.major_id = SP.object_id AND EP.minor_id = PM.parameter_id ) + + WHERE SP.object_id = @0 + + ORDER BY IP.ORDINAL_POSITION, IP.PARAMETER_NAME;", proc.ProcedureId); + + return this.peta.Fetch(sql); + } + } +} + +/* + +SELECT + PM.name AS ParameterName + , PM.max_length AS [MaximumLength] + , PM.precision AS [Precision] + , PM.scale AS [Scale] + , IP.DATA_TYPE AS DataType + , CASE + WHEN PM.is_output = 1 + THEN 'Output' + ELSE 'Input' + END AS Direction + + +FROM sys.procedures AS SP + INNER JOIN sys.parameters AS PM + ON ( SP.object_id = PM.object_id ) + INNER JOIN sys.schemas AS S + ON ( SP.schema_id = S.schema_id ) + INNER JOIN INFORMATION_SCHEMA.PARAMETERS AS IP + ON ( IP.SPECIFIC_SCHEMA = S.[name] AND IP.SPECIFIC_NAME = SP.[name] ) + +WHERE SP.object_id = @0 + +ORDER BY IP.ORDINAL_POSITION, IP.PARAMETER_NAME; + + +*/ \ No newline at end of file diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/ScalarFunctionInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/ScalarFunctionInspector.cs new file mode 100644 index 0000000..f857e9b --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/ScalarFunctionInspector.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PetaPoco; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class ScalarFunctionInspector : CommonInspector + { + + public ScalarFunctionInspector(PetaPoco.Database petaDb) : base(petaDb) + { + + } + + public IList GetScalarFunctions(Schema schema) + { + IList functionList = null; + + + functionList = this.queryForScalarFunctions(schema); + + + if (functionList != null && functionList.Count > 0) + { + var paramInspector = new ParameterInspector(this.peta); + + for (int f = 0; f < functionList.Count; f++) + { + var sf = functionList[f]; + sf.Parameters = paramInspector.GetParameters(sf); + sf.Parent = schema; + } + + } + + + return functionList; + + } + + private IList queryForScalarFunctions(Schema schema) + { + var sql = new Sql(@"SELECT + J.[name] AS FunctionName + , J.object_id AS FunctionId + , EP.value AS [Description] + , R.DATA_TYPE AS ReturnDataType + , R.CHARACTER_MAXIMUM_LENGTH AS ReturnTypeMaximumLength + , CONVERT(INT, R.NUMERIC_PRECISION) AS ReturnTypePrecision + , CONVERT(INT, R.NUMERIC_SCALE) AS ReturnTypeScale + + FROM sys.objects AS J + INNER JOIN sys.schemas AS S + ON ( J.schema_id = S.schema_id ) + INNER JOIN INFORMATION_SCHEMA.ROUTINES AS R + ON ( R.SPECIFIC_SCHEMA = S.[name] AND R.SPECIFIC_NAME = J.[name] ) + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 1 AND EP.name = 'MS_Description' AND EP.major_id = J.object_id AND EP.minor_id = 0 ) + + WHERE J.schema_id = @0 + AND J.[type] = 'FN' + + ORDER BY J.[name];", schema.SchemaId); + + return this.peta.Fetch(sql); + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/SchemaInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/SchemaInspector.cs new file mode 100644 index 0000000..4f951e8 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/SchemaInspector.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using PetaPoco; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class SchemaInspector : CommonInspector + { + + public SchemaInspector(PetaPoco.Database petaDb) : base(petaDb) + { + } + + + public IList GetSchemas(net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model.Database parent) + { + //get schemas + List schemaList = this.queryForSchemas(); + + //get tables for each schema + if (schemaList != null && schemaList.Count > 0) + { + var tableInspector = new TableInspector(this.peta); + + var viewInspector = new ViewInspector(this.peta); + + var sprocInspector = new StoredProcedureInspector(this.peta); + + var scalarUdfInspector = new ScalarFunctionInspector(this.peta); + + var tableUdfInspector = new TableFunctionInspector(this.peta); + + Schema schema = null; + + for (int i = 0; i < schemaList.Count; i++) + { + schema = schemaList[i]; + + schema.Tables = tableInspector.GetTables(schema); + schema.Views = viewInspector.GetViews(schema); + schema.StoredProcedures = sprocInspector.GetStoredProcedures(schema); + schema.ScalarFunctions = scalarUdfInspector.GetScalarFunctions(schema); + schema.TableFunctions = tableUdfInspector.GetTableFunctions(schema); + + schema.Parent = parent; + } + + } + + return schemaList; + } + + private List queryForSchemas() + { + //TODO: add description metadata from extended properties + + var sql = new Sql(@"SELECT S.[name] AS SchemaName + , S.schema_id AS SchemaId + , EP.value AS [Description] + FROM sys.schemas AS S + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( S.schema_id = EP.major_id AND EP.name = 'MS_Description' ) + + WHERE S.[name] NOT LIKE 'db__%' + AND S.[name] NOT IN ( 'sys', 'INFORMATION_SCHEMA' ) + + ORDER BY S.[name];"); + + return this.peta.Fetch(sql); + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/StoredProcedureInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/StoredProcedureInspector.cs new file mode 100644 index 0000000..9f880a1 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/StoredProcedureInspector.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PetaPoco; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class StoredProcedureInspector : CommonInspector + { + + public StoredProcedureInspector(PetaPoco.Database petaDb):base(petaDb) + { + + } + + public IList GetStoredProcedures(Schema schema) + { + IList spList = null; + + spList = this.queryForStoredProcedures(schema); + + if (spList != null && spList.Count > 0) + { + var parameterInspector = new ParameterInspector(this.peta); + + for (int p = 0; p < spList.Count; p++) + { + var proc = spList[p]; + proc.Parameters = parameterInspector.GetParameters(proc); + proc.Parent = schema; + } + } + + return spList; + } + + private IList queryForStoredProcedures(Schema schema) + { + var sql = new Sql(@"SELECT + SP.[name] AS ProcedureName + , SP.object_id AS ProcedureId + , EP.value AS [Description] + + FROM sys.procedures AS SP + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 1 AND EP.name = 'MS_Description' AND EP.major_id = SP.object_id AND EP.minor_id = 0 ) + + WHERE SP.schema_id = @0 + + ORDER BY SP.[name];", schema.SchemaId); + + return this.peta.Fetch(sql); + } + + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/TableFunctionInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/TableFunctionInspector.cs new file mode 100644 index 0000000..406c1a7 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/TableFunctionInspector.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PetaPoco; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + class TableFunctionInspector : CommonInspector + { + + + public TableFunctionInspector(PetaPoco.Database petaDb):base(petaDb) + { + + } + + + public IList GetTableFunctions(Schema schema) + { + IList tfList = this.queryForTableFunctions(schema); + + + if (tfList != null && tfList.Count > 0) + { + + var paramInspector = new ParameterInspector(this.peta); + + var columnInspector = new ColumnInspector(this.peta); + + for (int t = 0; t < tfList.Count; t++) + { + + var func = tfList[t]; + + func.Parameters = paramInspector.GetParameters(func); + + func.Columns = columnInspector.GetColumns(func); + + func.Parent = schema; + } + + } + + return tfList; + + } + + private IList queryForTableFunctions(Schema schema) + { + var sql = new Sql(@"SELECT + J.[name] AS FunctionName + , J.object_id AS FunctionId + , EP.value AS [Description] + + FROM sys.objects AS J + INNER JOIN sys.schemas AS S + ON ( J.schema_id = S.schema_id ) + INNER JOIN INFORMATION_SCHEMA.ROUTINES AS R + ON ( R.SPECIFIC_SCHEMA = S.[name] AND R.SPECIFIC_NAME = J.[name] ) + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.class = 1 AND EP.name = 'MS_Description' AND EP.major_id = J.object_id AND EP.minor_id = 0 ) + + WHERE J.schema_id = @0 + AND J.[type] IN ( 'TF', 'IF' ) + + ORDER BY J.[name];", schema.SchemaId); + + return this.peta.Fetch(sql); + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/TableInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/TableInspector.cs new file mode 100644 index 0000000..c7a9dd1 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/TableInspector.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using PetaPoco; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + class TableInspector : CommonInspector + { + + + public TableInspector(PetaPoco.Database petaDb):base(petaDb) + { + + + } + + public IList GetTables(Schema schema) + { + var tableList = this.queryForTables(schema); + + + + if (tableList != null && tableList.Count > 0) + { + Table table = null; + + var columnInspector = new ColumnInspector(this.peta); + + var indexInspector = new IndexInspector(this.peta); + + var foreignKeyInspector = new ForeignKeyInspector(this.peta); + + for (int i = 0; i < tableList.Count; i++) + { + table = tableList[i]; + table.Columns = columnInspector.GetColumns(table); + table.Indexes = indexInspector.GetIndexes(table); + table.ForeignKeys = foreignKeyInspector.GetForeignKeys(table); + table.Parent = schema; + table.RowCount = this.peta.ExecuteScalar("Select Sum(row_count) FROM sys.dm_db_partition_stats WHERE object_id = @0 AND (index_id = 0 OR index_id = 1)", table.TableId); + // We could store RowCount in Table class if needed in future + } + + } + + + return tableList; + + } + + + private IList
queryForTables(Schema schema) + { + var sql = new Sql(@"SELECT T.name AS TableName + , COALESCE(EP.value, '') AS [Description] + , T.object_id AS TableId + + FROM sys.tables AS T + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( EP.major_id = T.object_id AND EP.minor_id = 0 AND EP.name = 'MS_Description' ) + + WHERE T.schema_id = @0 + + ORDER BY T.name", schema.SchemaId); + + return this.peta.Fetch
(sql); + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Inspection/ViewInspector.cs b/SqlServerDatabaseDocumentationGenerator/Inspection/ViewInspector.cs new file mode 100644 index 0000000..b63c4ef --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Inspection/ViewInspector.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using PetaPoco; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Inspection +{ + public class ViewInspector : CommonInspector + { + + public ViewInspector(PetaPoco.Database petaDb):base(petaDb) + { + + } + + public IList GetViews(Schema schema) + { + IList viewList = this.getViewsForSchema(schema); + + + + if (viewList != null && viewList.Count > 0) + { + var columnInspector = new ColumnInspector(this.peta); + + var indexInspector = new IndexInspector(this.peta); + + for (int v = 0; v < viewList.Count; v++) + { + var view = viewList[v]; + + view.Columns = columnInspector.GetColumns(view); + + view.Indexes = indexInspector.GetIndexes(view); + + view.Parent = schema; + + } + + } + + + + + + return viewList; + } + + + private IList getViewsForSchema(Schema schema) + { + var sql = new Sql(@"SELECT + V.object_id AS ViewId + , V.[name] AS ViewName + , EP.value AS [Description] + + FROM sys.views AS V + LEFT OUTER JOIN sys.extended_properties AS EP + ON ( V.object_id = EP.major_id AND EP.class = 1 AND EP.minor_id = 0 AND EP.[name] = 'MS_Description' ) + + WHERE schema_id = @0 + + ORDER BY V.[name];", schema.SchemaId); + + return this.peta.Fetch(sql); + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/Column.cs b/SqlServerDatabaseDocumentationGenerator/Model/Column.cs new file mode 100644 index 0000000..b05e4eb --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/Column.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class Column : IDbObject + { + + + public string ColumnName { get; set; } + + + + /// + /// Built in data type or data type from which user defined type derives + /// + public string BaseDataTypeName { get; set; } + + public int? MaximumLength { get; set; } + + public int? Precision { get; set; } + + public int? Scale { get; set; } + + public bool AllowNull { get; set; } + + public string Description { get; set; } + + public int ColumnId { get; set; } + + public int ObjectId { get { return this.ColumnId; } } + + public string DefaultValue { get; set; } + + public bool IsIdentity { get; set; } + + public bool IsComputed { get; set; } + + //the TypeName value will be the same as BaseDataTypeName, unless this column data type is a user defined type + public string TypeName { get; set; } + + public bool IsUserDefinedType { get; set; } + + public IDbObject Parent { get; set; } + + //Display column names as Schema.TableName.ColumnName + public string ObjectFullDisplayName { get { return String.Format("{0}.{1}.{2}", this.Parent.Parent.ObjectName, this.Parent.ObjectName, this.ColumnName); } } + + public string ObjectName { get { return this.ColumnName; } } + + public string ObjectTypeDisplayText { get { return "Column"; } } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/Database.cs b/SqlServerDatabaseDocumentationGenerator/Model/Database.cs new file mode 100644 index 0000000..01657e0 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/Database.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class Database : IDbObject + { + + public string DatabaseName { get; set; } + + public int ObjectId { get; set; } + + public IList Schemas { get; set; } + + public string Description { get; set; } + + //Database does not have a parent object + public IDbObject Parent { get { return null; } set { } } + + public string ObjectName { get { return this.DatabaseName; } } + + public string ObjectFullDisplayName { get { return this.DatabaseName; } } + + public string ObjectTypeDisplayText { get { return "Database"; } } + + public IList DesignIssueWarnings { get; set; } + + /// + /// Get all IDbOjects in database in one list + /// + /// + public IList GetAllObjects() + { + List allDbObjList = new List(); + + + var schemas = ( + from s in this.Schemas + select s as IDbObject + ).ToList(); + + allDbObjList.AddRange(schemas); + + var tables = ( + from s in this.Schemas + from obj in s.Tables + select obj as IDbObject + ).ToList(); + + allDbObjList.AddRange(tables); + + var sprocs = ( + from s in this.Schemas + from obj in s.StoredProcedures + select obj as IDbObject + ).ToList(); + + if (sprocs.HasAny()) + { + var sprocParams = ( + from sc in this.Schemas + from sproc in sc.StoredProcedures + from param in sproc.Parameters + select param as IDbObject + ).ToList(); + + allDbObjList.AddRange(sprocParams); + } + + allDbObjList.AddRange(sprocs); + + var scalarFuncs = ( + from s in this.Schemas + from obj in s.ScalarFunctions + select obj as IDbObject + ).ToList(); + + if (scalarFuncs.HasAny()) + { + var scalarFuncParams = ( + from s in this.Schemas + from func in s.ScalarFunctions + from param in func.Parameters + select param as IDbObject + ).ToList(); + + allDbObjList.AddRange(scalarFuncParams); + + } + + allDbObjList.AddRange(scalarFuncs); + + + var tableFuncs = ( + from s in this.Schemas + from obj in s.TableFunctions + select obj as IDbObject + ).ToList(); + + if (tableFuncs.HasAny()) + { + var tfParams = ( + from s in this.Schemas + from tfunc in s.TableFunctions + from param in tfunc.Parameters + select param as IDbObject + ).ToList(); + + allDbObjList.AddRange(tfParams); + + + var tfCols = ( + from s in this.Schemas + from tfunc in s.TableFunctions + from param in tfunc.Parameters + select param as IDbObject + ).ToList(); + + allDbObjList.AddRange(tfCols); + + } + + allDbObjList.AddRange(tableFuncs); + + + return allDbObjList; + + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/DesignIssueWarning.cs b/SqlServerDatabaseDocumentationGenerator/Model/DesignIssueWarning.cs new file mode 100644 index 0000000..cd2856b --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/DesignIssueWarning.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class DesignIssueWarning + { + public List DatabaseObjects { get; set; } + + public string Description { get; set; } + + public Uri ReferenceUrl { get; set; } + + + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/ForeignKey.cs b/SqlServerDatabaseDocumentationGenerator/Model/ForeignKey.cs new file mode 100644 index 0000000..6db70d3 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/ForeignKey.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; + + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class ForeignKey : IDbObject + { + public string ObjectName { get { return this.ForeignKeyName; } } + + public string ObjectFullName { get { return this.ForeignKeyName; } } + + public int ForeignKeyId { get; set; } + + public string ForeignKeyName { get; set; } + + public int ReferencedObjectId { get; set; } + + public string ReferencedObjectName { get; set; } + + public string ReferencedObjectSchemaName { get; set; } + + public string DeleteReferentialAction { get; set; } + + public string UpdateReferentialAction { get; set; } + + public bool IsDisabled { get; set; } + + public bool IsNotTrusted { get; set; } + + public string Description { get; set; } + + public int ObjectId { get { return this.ForeignKeyId; } } + + public IList ForeignKeyColumns { get; set; } + + public IDbObject Parent { get; set; } + + public string ObjectFullDisplayName { get { return this.ObjectFullName; } } + + public string ObjectTypeDisplayText { get { return "Foreign Key"; } } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/ForeignKeyColumn.cs b/SqlServerDatabaseDocumentationGenerator/Model/ForeignKeyColumn.cs new file mode 100644 index 0000000..33aa57a --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/ForeignKeyColumn.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + /// + /// Column reference in foriegn key parent to reference + /// + public class ForeignKeyColumn + { + + public int ParentColumnId { get; set; } + + public string ParentColumnName { get; set; } + + public int ReferenceColumnId { get; set; } + + public string ReferenceColumnName { get; set; } + + public int ConstraintColumnId { get; set; } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/IDbObject.cs b/SqlServerDatabaseDocumentationGenerator/Model/IDbObject.cs new file mode 100644 index 0000000..2746c01 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/IDbObject.cs @@ -0,0 +1,30 @@ +using System; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public interface IDbObject + { + int ObjectId { get;} + + string Description { get; set; } + + /// + /// Parent of current object. For columns this would be a table or view, For tables this would be a schema, etc. + /// + IDbObject Parent { get; set; } + + string ObjectName { get; } + + /// + /// Object's display name + /// + /// dbo.spMySproc for a stored procedure in the 'dbo' schema with name of 'spMySproc' + string ObjectFullDisplayName { get; } + + /// + /// Object type type display text + /// + /// "Table", "Database", etc. + string ObjectTypeDisplayText { get; } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/IDbRoutine.cs b/SqlServerDatabaseDocumentationGenerator/Model/IDbRoutine.cs new file mode 100644 index 0000000..45e27e6 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/IDbRoutine.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + /// + /// Database routine object such as a stored procedure or function + /// + public interface IDbRoutine + { + IList Parameters { get; set; } + + string Description { get; set; } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/IUserDefinedFunction.cs b/SqlServerDatabaseDocumentationGenerator/Model/IUserDefinedFunction.cs new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/IUserDefinedFunction.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + /// + /// User defined function. Can be scalar or table + /// + public interface IUserDefinedFunction + { + + string FunctionName { get; set; } + + int FunctionId { get; set; } + + string Description { get; set; } + + IList Parameters { get; set; } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/Index.cs b/SqlServerDatabaseDocumentationGenerator/Model/Index.cs new file mode 100644 index 0000000..616f79d --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/Index.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class Index : IDbObject + { + public int IndexId { get; set; } + + public int ObjectId { get { return this.IndexId; } } + + public string IndexName { get; set; } + + public IList ColumnNames { get; set; } + + public string Description { get; set; } + + public bool IsPrimaryKey { get; set; } + + public bool IsUnique { get; set; } + + public string IndexTypeDescription { get; set; } + + public IDbObject Parent { get; set; } + + public string ObjectName { get { return this.IndexName; } } + + public string ObjectFullDisplayName { get { return this.IndexName; } } + + public string ObjectTypeDisplayText { get { return "Index"; } } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/Parameter.cs b/SqlServerDatabaseDocumentationGenerator/Model/Parameter.cs new file mode 100644 index 0000000..5751653 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/Parameter.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class Parameter : IDbObject + { + public string ParameterName { get; set; } + + public string DataType { get; set; } + + public int? MaximumLength { get; set; } + + public int? Precision { get; set; } + + public int? Scale { get; set; } + + public string Direction { get; set; } + + public string Description { get; set; } + + public IDbObject Parent { get; set; } + + public int ObjectId { get; set; } + + public string ObjectName { get { return this.ParameterName; } } + + public string ObjectFullDisplayName { get { return this.ParameterName; } } + + public string ObjectTypeDisplayText { get { return String.Format("Parameter of {0}", this.Parent.ObjectFullDisplayName); } } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/ScalarFunction.cs b/SqlServerDatabaseDocumentationGenerator/Model/ScalarFunction.cs new file mode 100644 index 0000000..1e9a04b --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/ScalarFunction.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class ScalarFunction: IDbObject, IUserDefinedFunction, IDbRoutine + { + + public string FunctionName { get; set; } + + public int FunctionId { get; set; } + + public string Description { get; set; } + + public IList Parameters { get; set; } + + public int ObjectId { get { return this.FunctionId; } } + + public string ReturnDataType { get; set; } + + public int? ReturnTypeMaximumLength { get; set; } + + public int? ReturnTypePrecision { get; set; } + + public int? ReturnTypeScale { get; set; } + + public IDbObject Parent { get; set; } + + public string ObjectName { get { return this.FunctionName; } } + + public string ObjectFullDisplayName { get { return String.Format("{0}.{1}", this.Parent.ObjectName, this.ObjectName); } } + + public string ObjectTypeDisplayText { get { return "Function (scalar)"; } } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/Schema.cs b/SqlServerDatabaseDocumentationGenerator/Model/Schema.cs new file mode 100644 index 0000000..7558f08 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/Schema.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class Schema : IDbObject + { + public string ObjectName { get { return this.SchemaName; } } + + public string ObjectFullDisplayName { get { return this.ObjectName; } } + + public string SchemaName { get; set; } + + public int SchemaId { get; set; } + + public string Description { get; set; } + + public IList
Tables { get; set; } + + public IList Views { get; set; } + + public IList StoredProcedures { get; set; } + + public IList ScalarFunctions { get; set; } + + public IList TableFunctions { get; set; } + + public int ObjectId { get { return this.SchemaId; } } + + public IDbObject Parent { get; set; } + + public string ObjectTypeDisplayText { get { return "Schema"; } } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/StoredProcedure.cs b/SqlServerDatabaseDocumentationGenerator/Model/StoredProcedure.cs new file mode 100644 index 0000000..ae17247 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/StoredProcedure.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class StoredProcedure : IDbObject, IDbRoutine + { + public string ProcedureName {get;set; } + + public int ProcedureId {get;set; } + + public int ObjectId { get { return this.ProcedureId; } } + + public string Description { get; set; } + + public IList Parameters { get; set; } + + public IDbObject Parent { get; set; } + + public string ObjectName { get { return this.ProcedureName; } } + + public string ObjectFullDisplayName { get { return String.Format("{0}.{1}", this.Parent.ObjectName, this.ObjectName); } } + + public string ObjectTypeDisplayText { get { return "Stored Procedure"; } } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/Table.cs b/SqlServerDatabaseDocumentationGenerator/Model/Table.cs new file mode 100644 index 0000000..cf4701c --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/Table.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class Table : IDbObject + { + public string TableName { get; set; } + + public string Description { get; set; } + + public int TableId { get; set; } + + public int ObjectId { get { return this.TableId; } } + + public IList Columns { get; set; } + + public IList Indexes { get; set; } + + public IList ForeignKeys { get; set; } + + public IDbObject Parent { get; set; } + + public string ObjectName { get { return this.TableName; } } + + public string ObjectFullDisplayName { get { return String.Format("{0}.{1}", this.Parent.ObjectName, this.ObjectName); } } + + public string ObjectTypeDisplayText { get { return "Table"; } } + + public int RowCount { get; set; } + + /// + /// Check if the table has any columns which use a user defined data type + /// + /// + public bool ContainsColumnsWithUserDefinedDataType() + { + bool result = false; + + if (this.Columns.HasAny()) + { + if (this.Columns.Where(c => c.IsUserDefinedType).Any()) + { + result = true; + } + } + + return result; + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/TableFunction.cs b/SqlServerDatabaseDocumentationGenerator/Model/TableFunction.cs new file mode 100644 index 0000000..4eae062 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/TableFunction.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class TableFunction : IDbObject, IDbRoutine, IUserDefinedFunction + { + public string FunctionName { get; set; } + + public int FunctionId { get; set; } + + public string Description { get; set; } + + public IList Parameters { get; set; } + + public int ObjectId { get { return this.FunctionId; } } + + public IList Columns { get; set; } + + public IDbObject Parent { get; set; } + + public string ObjectName { get { return this.FunctionName; } } + + public string ObjectFullDisplayName { get { return String.Format("{0}.{1}", this.Parent.ObjectName, this.ObjectName); } } + + public string ObjectTypeDisplayText { get { return "Function (table)"; } } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Model/View.cs b/SqlServerDatabaseDocumentationGenerator/Model/View.cs new file mode 100644 index 0000000..f9502cc --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Model/View.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model +{ + public class View :IDbObject + { + public string ObjectName { get { return this.ViewName; } } + + public int ViewId { get; set; } + + public string ViewName { get; set; } + + public string Description { get; set; } + + public IList Columns { get; set; } + + public IList Indexes { get; set; } + + public IDbObject Parent { get; set; } + + public bool IsIndexedView + { + get + { + return ( this.Indexes != null && this.Indexes.Count > 0 ); + } + } + + + public int ObjectId { get { return this.ViewId; } + } + + public string ObjectFullDisplayName { get { return String.Format("{0}.{1}", this.Parent.ObjectName, this.ObjectName); } } + + public string ObjectTypeDisplayText { get { return "View"; } } + + /// + /// Check if the table has any columns which use a user defined data type + /// + /// + public bool ContainsColumnsWithUserDefinedDataType() + { + bool result = false; + + if (this.Columns.HasAny()) + { + if (this.Columns.Where(c => c.IsUserDefinedType).Any()) + { + result = true; + } + } + + return result; + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Properties/AssemblyInfo.cs b/SqlServerDatabaseDocumentationGenerator/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..273dca6 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SqlServerDatabaseDocumentationGenerator")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Jeremy Knue")] +[assembly: AssemblyProduct("SqlServerDatabaseDocumentationGenerator")] +[assembly: AssemblyCopyright("Copyright © Jeremy Knue 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6d1e66bb-b85a-4199-afda-58162f704d60")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.4.1.0")] +//[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SqlServerDatabaseDocumentationGenerator/SqlServerDatabaseDocumentationGenerator.csproj b/SqlServerDatabaseDocumentationGenerator/SqlServerDatabaseDocumentationGenerator.csproj new file mode 100644 index 0000000..3c7ce98 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/SqlServerDatabaseDocumentationGenerator.csproj @@ -0,0 +1,20 @@ + + + + + net48 + Library + net.datacowboy.SqlServerDatabaseDocumentationGenerator + SqlServerDatabaseDocumentationGenerator + + false + + + + + + Always + + + + \ No newline at end of file diff --git a/SqlServerDatabaseDocumentationGenerator/ToDoList.txt b/SqlServerDatabaseDocumentationGenerator/ToDoList.txt new file mode 100644 index 0000000..521528e --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/ToDoList.txt @@ -0,0 +1,36 @@ +To Do List + + +* Support finding objects with out a description and scripting the SQL commands to add descriptions. + * mostly done in 1.0.0.0 still need to do parameters on udfs and sprocs + +* In HTML documentation link data types to descriptions on MSDN or Technet + +* In HTML add internal links to navigate between objects + +* DONE Implement Sortable Binding List on grid in FrmObjectsWithoutDescription. + see: http://msdn.microsoft.com/en-us/library/ms993236.aspx + +* Add support for finding objects with names that include special characters + +* Make adding design issue warnings to UI optional + +* Add support for showing contraints on columns + +* DONE: Support user defined functions: scalar and table + +* DONE: Support views + +* DONE: Support indexes - done on tables. - done on views. + +* DONE: Support foreign keys on tables + +* DONE: Re-factor ColumnInspector query to remove duplicate column records with different descriptions. One description is probably related to the index, not the table column + +* DONE: In DatabaseHtmlDocumentGenerator replace Write() with WriteEncodedText() to escape reserved entities. + +* DONE: In WinForm UI: Increase width of connection string textbox control + +* DONE: In WinForm UI: Add connection string wizard + + diff --git a/SqlServerDatabaseDocumentationGenerator/Utility/ConnectionTestResult.cs b/SqlServerDatabaseDocumentationGenerator/Utility/ConnectionTestResult.cs new file mode 100644 index 0000000..ad9cc9f --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Utility/ConnectionTestResult.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility +{ + + public class ConnectionTestResult + { + public bool Success { get; set; } + + public string ErrorMessage { get; set; } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Utility/ExtendedPropertyExtension.cs b/SqlServerDatabaseDocumentationGenerator/Utility/ExtendedPropertyExtension.cs new file mode 100644 index 0000000..cbdf0e6 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Utility/ExtendedPropertyExtension.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility +{ + /// + /// Collection of extenision methods to generate SQL scripts for extended properties + /// + public static class ExtendedPropertyExtension + { + private static string createAddExtendedPropertySprocTextForMsDescription(string description, string level0type=null, string level0name=null, string level1type = null, string level1name = null, string level2type = null, string level2name = null) + { + var sb = new StringBuilder("EXEC sp_addextendedproperty @name='MS_Description'"); + sb.Append(" , @value="); + sb.Append(description.ToSqlQuotedText()); + + if (!String.IsNullOrWhiteSpace(level0name) && !String.IsNullOrWhiteSpace(level0type)) + { + sb.Append(" , @level0type="); + sb.Append(level0type.ToSqlQuotedText()); + sb.Append(" , @level0name="); + sb.Append(level0name.ToSqlQuotedText()); + } + + + if (!String.IsNullOrWhiteSpace(level1name) && !String.IsNullOrWhiteSpace(level1type)) + { + sb.Append(" , @level1type="); + sb.Append(level1type.ToSqlQuotedText()); + sb.Append(" , @level1name="); + sb.Append(level1name.ToSqlQuotedText()); + } + + if (!String.IsNullOrWhiteSpace(level2name) && !String.IsNullOrWhiteSpace(level2type)) + { + sb.Append(" , @level2type="); + sb.Append(level2type.ToSqlQuotedText()); + sb.Append(" , @level2name="); + sb.Append(level2name.ToSqlQuotedText()); + } + + sb.Append(";"); + + return sb.ToString(); + } + + public static string CreateIDbObjectDescriptionSqlCommandText(this IDbObject obj) + { + if (obj is Database) + { + return (obj as Database).CreateDescriptionSqlCommandText(); + } + + if (obj is Schema) + { + return (obj as Schema).CreateDescriptionSqlCommandText(); + } + + if (obj is Table) + { + return (obj as Table).CreateDescriptionSqlCommandText(); + } + + if (obj is View) + { + return (obj as View).CreateDescriptionSqlCommandText(); + } + + if (obj is StoredProcedure) + { + return (obj as StoredProcedure).CreateDescriptionSqlCommandText(); + } + + if (obj is ScalarFunction) + { + return (obj as ScalarFunction).CreateDescriptionSqlCommandText(); + } + + if (obj is TableFunction) + { + return (obj as TableFunction).CreateDescriptionSqlCommandText(); + } + + if (obj is Column) + { + return (obj as Column).CreateDescriptionSqlCommandText(); + } + + if (obj is Parameter) + { + return (obj as Parameter).CreateDescriptionSqlCommandText(); + } + + return String.Empty; + } + + public static string CreateDescriptionSqlCommandText(this Database db) + { + return createAddExtendedPropertySprocTextForMsDescription(db.Description); + } + + public static string CreateDescriptionSqlCommandText(this View view) + { + return createAddExtendedPropertySprocTextForMsDescription(view.Description, "SCHEMA", view.Parent.ObjectName, "VIEW", view.ViewName); + } + + + public static string CreateDescriptionSqlCommandText(this Schema schema) + { + return createAddExtendedPropertySprocTextForMsDescription(schema.Description, "SCHEMA", schema.SchemaName); + } + + public static string CreateDescriptionSqlCommandText(this Table table) + { + return createAddExtendedPropertySprocTextForMsDescription(table.Description, "SCHEMA", table.Parent.ObjectName, "TABLE", table.TableName); + } + + public static string CreateDescriptionSqlCommandText(this StoredProcedure sproc) + { + return createAddExtendedPropertySprocTextForMsDescription(sproc.Description, "SCHEMA", sproc.Parent.ObjectName, "PROCEDURE", sproc.ObjectName); + } + + public static string CreateDescriptionSqlCommandText(this ScalarFunction func) + { + return createAddExtendedPropertySprocTextForMsDescription(func.Description, "SCHEMA", func.Parent.ObjectName, "FUNCTION", func.FunctionName); + } + + public static string CreateDescriptionSqlCommandText(this TableFunction func) + { + return createAddExtendedPropertySprocTextForMsDescription(func.Description, "SCHEMA", func.Parent.ObjectName, "FUNCTION", func.FunctionName); + } + + /// + /// Applied only to stored procedure parameters + /// + /// + /// + public static string CreateDescriptionSqlCommandText(this Parameter param) + { + return createAddExtendedPropertySprocTextForMsDescription(param.Description, "SCHEMA", param.Parent.Parent.ObjectName, "PROCEDURE", param.Parent.ObjectName, "PARAMETER", param.ParameterName); + } + + public static string CreateDescriptionSqlCommandText(this Column column) + { + //only applies to columns owned by a table + if (column.Parent is Table) + { + return createAddExtendedPropertySprocTextForMsDescription(column.Description, "SCHEMA", column.Parent.Parent.ObjectName, "TABLE", column.Parent.ObjectName, "COLUMN", column.ColumnName); + } + else + { + return String.Empty; + } + + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Utility/IDbObjectExtension.cs b/SqlServerDatabaseDocumentationGenerator/Utility/IDbObjectExtension.cs new file mode 100644 index 0000000..0320719 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Utility/IDbObjectExtension.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility +{ + /// + /// Collection of extension methods for IDbObject + /// + public static class IDbObjectExtension + { + + public static IList FindObjectsWithoutDescription(this IList objList) + { + return (from obj in objList + where String.IsNullOrWhiteSpace(obj.Description) == true + select obj).ToList(); + + + + } + + public static IList FindObjectsWithoutDescriptionInDatabase(this Database database) + { + var objList = new List(); + + if (String.IsNullOrWhiteSpace(database.Description)) + { + objList.Add(database); + } + + if (database.Schemas != null && database.Schemas.Count > 0) + { + for (int i = 0; i < database.Schemas.Count; i++) + { + objList.AddRange(database.Schemas[i].FindObjectsWithoutDescriptionInSchema()); + } + } + + return objList; + + } + + public static IList FindObjectsWithoutDescriptionInSchema(this Schema schema) + { + var objList = new List(); + + if (schema.Tables != null && schema.Tables.Count > 0) + { + objList.AddRange(schema.Tables.ToArray().FindObjectsWithoutDescription()); + + //get columns + for (int t = 0; t < schema.Tables.Count; t++) + { + var table = schema.Tables[t]; + objList.AddRange(table.Columns.ToArray().FindObjectsWithoutDescription()); + } + + } + + if (schema.Views.HasAny()) + { + objList.AddRange(schema.Views.ToArray().FindObjectsWithoutDescription()); + } + + if (schema.StoredProcedures.HasAny()) + { + + var spList = schema.StoredProcedures.ToArray().FindObjectsWithoutDescription(); + + if (spList.HasAny()) + { + for (int p = 0; p < spList.Count(); p++) + { + objList.Add(spList[p]); + + StoredProcedure sproc = spList[p] as StoredProcedure; + + var paramList = sproc.Parameters.ToArray().FindObjectsWithoutDescription(); + + if (paramList.HasAny()) + { + objList.AddRange(paramList); + } + + } + } + + + + } + + if (schema.TableFunctions.HasAny() ) + { + objList.AddRange(schema.TableFunctions.ToArray().FindObjectsWithoutDescription()); + } + + if (schema.ScalarFunctions.HasAny()) + { + objList.AddRange(schema.ScalarFunctions.ToArray().FindObjectsWithoutDescription()); + } + + return objList; + + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Utility/IEnumerableExtension.cs b/SqlServerDatabaseDocumentationGenerator/Utility/IEnumerableExtension.cs new file mode 100644 index 0000000..8f9987b --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Utility/IEnumerableExtension.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility +{ + public static class IEnumerableExtension + { + /// + /// Check if a set is not null and has any members + /// + /// + /// + /// + public static bool HasAny(this IEnumerable input) + { + return (input != null && input.Any()); + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Utility/SqlConnectionTester.cs b/SqlServerDatabaseDocumentationGenerator/Utility/SqlConnectionTester.cs new file mode 100644 index 0000000..66bbcb6 --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Utility/SqlConnectionTester.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using System.Data.SqlClient; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility +{ + public static class SqlConnectionTester + { + + public static ConnectionTestResult TestConnectionString(string connectionString, bool closeConnection = false) + { + + var result = new ConnectionTestResult(); + + if (!String.IsNullOrWhiteSpace(connectionString)) + { + + + SqlConnection sqlConnection = new SqlConnection(connectionString); + + try + { + sqlConnection.Open(); + + if (closeConnection) + { + sqlConnection.Close(); + } + + + result.Success = true; + } + catch (Exception ex) + { + result.Success = false; + result.ErrorMessage = ex.Message; + } + + } + else + { + result.Success = false; + result.ErrorMessage = "Connection string cannot be empty"; + } + + return result; + } + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/Utility/SqlExtension.cs b/SqlServerDatabaseDocumentationGenerator/Utility/SqlExtension.cs new file mode 100644 index 0000000..638a94c --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/Utility/SqlExtension.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility +{ + /// + /// Collection of extension methods related to SQL text + /// + public static class SqlExtension + { + /// + /// Quote and escape string for use in SQL script as text + /// + /// + /// + /// + /// Will treat a null value as an empty string + /// + public static string ToSqlQuotedText(this string input) + { + //remove known potentially dangerous strings + var sb = new StringBuilder(input.StripSqlUnsafeCharacters()); + + sb.Replace("'", "''"); //escape single quotes within text + + //surround text with single quotes + sb.Insert(0, "'"); + sb.Append("'"); + + return sb.ToString(); + } + + + + /// + /// Remove potentially dangerous strings from text. + /// + /// + /// + /// + /// Intended for use when generating SQL script files. Not as a substitute for parameterized SQL statments. + /// Based on list from: http://technet.microsoft.com/en-us/library/ms161953(v=SQL.105).aspx + /// + public static string StripSqlUnsafeCharacters(this string input) + { + var escapeList = new string[] { ";", "'", "--", "/*", "*/", "xp_" }; + + + if (!String.IsNullOrWhiteSpace(input)) + { + var sb = new StringBuilder(input); + + for (int i = 0; i < escapeList.Length; i++) + { + sb.Replace(escapeList[i], String.Empty); + } + + + return sb.ToString(); + } + else + { + return input; + } + + } + + } +} diff --git a/SqlServerDatabaseDocumentationGenerator/third-party/PetaPoco.cs b/SqlServerDatabaseDocumentationGenerator/third-party/PetaPoco.cs new file mode 100644 index 0000000..06f312f --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/third-party/PetaPoco.cs @@ -0,0 +1,2376 @@ +/* PetaPoco v4.0.3 - A Tiny ORMish thing for your POCO's. + * Copyright © 2011 Topten Software. All Rights Reserved. + * + * Apache License 2.0 - http://www.toptensoftware.com/petapoco/license + * + * Special thanks to Rob Conery (@robconery) for original inspiration (ie:Massive) and for + * use of Subsonic's T4 templates, Rob Sullivan (@DataChomp) for hard core DBA advice + * and Adam Schroder (@schotime) for lots of suggestions, improvements and Oracle support + */ + +// Define PETAPOCO_NO_DYNAMIC in your project settings on .NET 3.5 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Configuration; +using System.Data.Common; +using System.Data; +using System.Text.RegularExpressions; +using System.Reflection; +using System.Reflection.Emit; +using System.Linq.Expressions; + + +namespace PetaPoco +{ + // Poco's marked [Explicit] require all column properties to be marked + [AttributeUsage(AttributeTargets.Class)] + public class ExplicitColumnsAttribute : Attribute + { + } + // For non-explicit pocos, causes a property to be ignored + [AttributeUsage(AttributeTargets.Property)] + public class IgnoreAttribute : Attribute + { + } + + // For explicit pocos, marks property as a column and optionally supplies column name + [AttributeUsage(AttributeTargets.Property)] + public class ColumnAttribute : Attribute + { + public ColumnAttribute() { } + public ColumnAttribute(string name) { Name = name; } + public string Name { get; set; } + } + + // For explicit pocos, marks property as a indexList column and optionally supplies column name + [AttributeUsage(AttributeTargets.Property)] + public class ResultColumnAttribute : ColumnAttribute + { + public ResultColumnAttribute() { } + public ResultColumnAttribute(string name) : base(name) { } + } + + // Specify the table name of a poco + [AttributeUsage(AttributeTargets.Class)] + public class TableNameAttribute : Attribute + { + public TableNameAttribute(string tableName) + { + Value = tableName; + } + public string Value { get; private set; } + } + + // Specific the primary key of a poco class (and optional sequence name for Oracle) + [AttributeUsage(AttributeTargets.Class)] + public class PrimaryKeyAttribute : Attribute + { + public PrimaryKeyAttribute(string primaryKey) + { + Value = primaryKey; + autoIncrement = true; + } + + public string Value { get; private set; } + public string sequenceName { get; set; } + public bool autoIncrement { get; set; } + } + + [AttributeUsage(AttributeTargets.Property)] + public class AutoJoinAttribute : Attribute + { + public AutoJoinAttribute() { } + } + + // Results from paged request + public class Page + { + public long CurrentPage { get; set; } + public long TotalPages { get; set; } + public long TotalItems { get; set; } + public long ItemsPerPage { get; set; } + public List Items { get; set; } + public object Context { get; set; } + } + + // Pass as parameter value to force to DBType.AnsiString + public class AnsiString + { + public AnsiString(string str) + { + Value = str; + } + public string Value { get; private set; } + } + + // Used by IMapper to override table bindings for an object + public class TableInfo + { + public string TableName { get; set; } + public string PrimaryKey { get; set; } + public bool AutoIncrement { get; set; } + public string SequenceName { get; set; } + } + + // Optionally provide an implementation of this to Database.Mapper + public interface IMapper + { + void GetTableInfo(Type t, TableInfo ti); + bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn); + Func GetFromDbConverter(PropertyInfo pi, Type SourceType); + Func GetToDbConverter(Type SourceType); + } + + // This will be merged with IMapper in the next major version + public interface IMapper2 : IMapper + { + Func GetFromDbConverter(Type DestType, Type SourceType); + } + + // Database class ... this is where most of the action happens + public class Database : IDisposable + { + public Database(IDbConnection connection) + { + _sharedConnection = connection; + _connectionString = connection.ConnectionString; + _sharedConnectionDepth = 2; // Prevent closing external connection + CommonConstruct(); + } + + public Database(string connectionString, string providerName) + { + _connectionString = connectionString; + _providerName = providerName; + CommonConstruct(); + } + + public Database(string connectionString, DbProviderFactory provider) + { + _connectionString = connectionString; + _factory = provider; + CommonConstruct(); + } + + public Database(string connectionStringName) + { + // Use first? + if (connectionStringName == "") + { + var first = GetConnectionStringByIndex(0); + if (first == null) + throw new InvalidOperationException("Can't find any connection strings in configuration."); + var nameProp = first.GetType().GetProperty("Name"); + connectionStringName = nameProp == null ? "" : (string)nameProp.GetValue(first, null); + } + + // Work out connection string and provider name + var providerName = "System.Data.SqlClient"; + var cs = GetConnectionStringByName(connectionStringName); + if (cs != null) + { + var provProp = cs.GetType().GetProperty("ProviderName"); + var provVal = provProp == null ? null : provProp.GetValue(cs, null) as string; + if (!string.IsNullOrEmpty(provVal)) + providerName = provVal; + } + else + { + throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'"); + } + + // Store factory and connection string + var connProp = cs.GetType().GetProperty("ConnectionString"); + _connectionString = connProp == null ? null : connProp.GetValue(cs, null) as string; + _providerName = providerName; + CommonConstruct(); + } + + // Reflection-based helpers to access ConfigurationManager.ConnectionStrings without a compile-time dependency + private static object GetConnectionStringByName(string name) + { + Type cfgType = Type.GetType("System.Configuration.ConfigurationManager, System.Configuration") + ?? Type.GetType("System.Configuration.ConfigurationManager, System.Configuration.ConfigurationManager"); + if (cfgType == null) + return null; + var connStringsProp = cfgType.GetProperty("ConnectionStrings", BindingFlags.Static | BindingFlags.Public); + if (connStringsProp == null) + return null; + var connStrings = connStringsProp.GetValue(null, null); + if (connStrings == null) + return null; + var itemProp = connStrings.GetType().GetProperty("Item", new Type[] { typeof(string) }); + if (itemProp != null) + return itemProp.GetValue(connStrings, new object[] { name }); + return null; + } + + private static object GetConnectionStringByIndex(int index) + { + Type cfgType = Type.GetType("System.Configuration.ConfigurationManager, System.Configuration") + ?? Type.GetType("System.Configuration.ConfigurationManager, System.Configuration.ConfigurationManager"); + if (cfgType == null) + return null; + var connStringsProp = cfgType.GetProperty("ConnectionStrings", BindingFlags.Static | BindingFlags.Public); + if (connStringsProp == null) + return null; + var connStrings = connStringsProp.GetValue(null, null); + if (connStrings == null) + return null; + var itemProp = connStrings.GetType().GetProperty("Item", new Type[] { typeof(int) }); + if (itemProp != null) + return itemProp.GetValue(connStrings, new object[] { index }); + return null; + } + + enum DBType + { + SqlServer, + SqlServerCE, + MySql, + PostgreSQL, + Oracle, + SQLite + } + DBType _dbType = DBType.SqlServer; + + // Common initialization + private void CommonConstruct() + { + _transactionDepth = 0; + EnableAutoSelect = true; + EnableNamedParams = true; + ForceDateTimesToUtc = true; + + if (_providerName != null) + _factory = DbProviderFactories.GetFactory(_providerName); + + string dbtype = (_factory == null ? _sharedConnection.GetType() : _factory.GetType()).Name; + + // Try using type name first (more reliable) + if (dbtype.StartsWith("MySql")) _dbType = DBType.MySql; + else if (dbtype.StartsWith("SqlCe")) _dbType = DBType.SqlServerCE; + else if (dbtype.StartsWith("Npgsql")) _dbType = DBType.PostgreSQL; + else if (dbtype.StartsWith("Oracle")) _dbType = DBType.Oracle; + else if (dbtype.StartsWith("SQLite")) _dbType = DBType.SQLite; + else if (dbtype.StartsWith("System.Data.SqlClient.")) _dbType = DBType.SqlServer; + // else try with provider name + else if (_providerName.IndexOf("MySql", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.MySql; + else if (_providerName.IndexOf("SqlServerCe", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.SqlServerCE; + else if (_providerName.IndexOf("Npgsql", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.PostgreSQL; + else if (_providerName.IndexOf("Oracle", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.Oracle; + else if (_providerName.IndexOf("SQLite", StringComparison.InvariantCultureIgnoreCase) >= 0) _dbType = DBType.SQLite; + + if (_dbType == DBType.MySql && _connectionString != null && _connectionString.IndexOf("Allow User Variables=true") >= 0) + _paramPrefix = "?"; + if (_dbType == DBType.Oracle) + _paramPrefix = ":"; + } + + // Automatically close one open shared connection + public void Dispose() + { + // Automatically close one open connection reference + // (Works with KeepConnectionAlive and manually opening a shared connection) + CloseSharedConnection(); + } + + // Set to true to keep the first opened connection alive until this object is disposed + public bool KeepConnectionAlive { get; set; } + + // Open a connection (can be nested) + public void OpenSharedConnection() + { + if (_sharedConnectionDepth == 0) + { + _sharedConnection = _factory.CreateConnection(); + _sharedConnection.ConnectionString = _connectionString; + _sharedConnection.Open(); + + _sharedConnection = OnConnectionOpened(_sharedConnection); + + if (KeepConnectionAlive) + _sharedConnectionDepth++; // Make sure you call Dispose + } + _sharedConnectionDepth++; + } + + // Close a previously opened connection + public void CloseSharedConnection() + { + if (_sharedConnectionDepth > 0) + { + _sharedConnectionDepth--; + if (_sharedConnectionDepth == 0) + { + OnConnectionClosing(_sharedConnection); + _sharedConnection.Dispose(); + _sharedConnection = null; + } + } + } + + // Access to our shared connection + public IDbConnection Connection + { + get { return _sharedConnection; } + } + + // Helper to create a transaction scope + public Transaction GetTransaction() + { + return new Transaction(this); + } + + // Use by derived repo generated by T4 templates + public virtual void OnBeginTransaction() { } + public virtual void OnEndTransaction() { } + + // Start a new transaction, can be nested, every call must be + // matched by a call to AbortTransaction or CompleteTransaction + // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics + public void BeginTransaction() + { + _transactionDepth++; + + if (_transactionDepth == 1) + { + OpenSharedConnection(); + _transaction = _sharedConnection.BeginTransaction(); + _transactionCancelled = false; + OnBeginTransaction(); + } + + } + + // Internal helper to cleanup transaction stuff + void CleanupTransaction() + { + OnEndTransaction(); + + if (_transactionCancelled) + _transaction.Rollback(); + else + _transaction.Commit(); + + _transaction.Dispose(); + _transaction = null; + + CloseSharedConnection(); + } + + // Abort the entire outer most transaction scope + public void AbortTransaction() + { + _transactionCancelled = true; + if ((--_transactionDepth) == 0) + CleanupTransaction(); + } + + // Complete the transaction + public void CompleteTransaction() + { + if ((--_transactionDepth) == 0) + CleanupTransaction(); + } + + // Helper to handle named parameters from object properties + static Regex rxParams = new Regex(@"(? args_dest) + { + return rxParams.Replace(_sql, m => + { + string param = m.Value.Substring(1); + + object arg_val; + + int paramIndex; + if (int.TryParse(param, out paramIndex)) + { + // Numbered parameter + if (paramIndex < 0 || paramIndex >= args_src.Length) + throw new ArgumentOutOfRangeException(string.Format("Parameter '@{0}' specified but only {1} parameters supplied (in `{2}`)", paramIndex, args_src.Length, _sql)); + arg_val = args_src[paramIndex]; + } + else + { + // Look for a property on one of the arguments with this name + bool found = false; + arg_val = null; + foreach (var o in args_src) + { + var pi = o.GetType().GetProperty(param); + if (pi != null) + { + arg_val = pi.GetValue(o, null); + found = true; + break; + } + } + + if (!found) + throw new ArgumentException(string.Format("Parameter '@{0}' specified but none of the passed arguments have a property with this name (in '{1}')", param, _sql)); + } + + // Expand collections to parameter lists + if ((arg_val as System.Collections.IEnumerable) != null && + (arg_val as string) == null && + (arg_val as byte[]) == null) + { + var sb = new StringBuilder(); + foreach (var i in arg_val as System.Collections.IEnumerable) + { + sb.Append((sb.Length == 0 ? "@" : ",@") + args_dest.Count.ToString()); + args_dest.Add(i); + } + return sb.ToString(); + } + else + { + args_dest.Add(arg_val); + return "@" + (args_dest.Count - 1).ToString(); + } + } + ); + } + + // Add a parameter to a DB command + void AddParam(IDbCommand cmd, object item, string ParameterPrefix) + { + // Convert value to from poco type to db type + if (Database.Mapper != null && item!=null) + { + var fn = Database.Mapper.GetToDbConverter(item.GetType()); + if (fn!=null) + item = fn(item); + } + + // Support passed in parameters + var idbParam = item as IDbDataParameter; + if (idbParam != null) + { + idbParam.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count); + cmd.Parameters.Add(idbParam); + return; + } + + var p = cmd.CreateParameter(); + p.ParameterName = string.Format("{0}{1}", ParameterPrefix, cmd.Parameters.Count); + if (item == null) + { + p.Value = DBNull.Value; + } + else + { + var t = item.GetType(); + if (t.IsEnum) // PostgreSQL .NET driver wont cast enum to int + { + p.Value = (int)item; + } + else if (t == typeof(Guid)) + { + p.Value = item.ToString(); + p.DbType = DbType.String; + p.Size = 40; + } + else if (t == typeof(string)) + { + p.Size = Math.Max((item as string).Length + 1, 4000); // Help query plan caching by using common size + p.Value = item; + } + else if (t == typeof(AnsiString)) + { + // Thanks @DataChomp for pointing out the SQL Server indexing performance hit of using wrong string type on varchar + p.Size = Math.Max((item as AnsiString).Value.Length + 1, 4000); + p.Value = (item as AnsiString).Value; + p.DbType = DbType.AnsiString; + } + else if (t == typeof(bool) && _dbType != DBType.PostgreSQL) + { + p.Value = ((bool)item) ? 1 : 0; + } + else if (item.GetType().Name == "SqlGeography") //SqlGeography is a CLR Type + { + p.GetType().GetProperty("UdtTypeName").SetValue(p, "geography", null); //geography is the equivalent SQL Server Type + p.Value = item; + } + + else if (item.GetType().Name == "SqlGeometry") //SqlGeometry is a CLR Type + { + p.GetType().GetProperty("UdtTypeName").SetValue(p, "geometry", null); //geography is the equivalent SQL Server Type + p.Value = item; + } + else + { + p.Value = item; + } + } + + cmd.Parameters.Add(p); + } + + // Create a command + static Regex rxParamsPrefix = new Regex(@"(?(); + sql = ProcessParams(sql, args, new_args); + args = new_args.ToArray(); + } + + // Perform parameter prefix replacements + if (_paramPrefix != "@") + sql = rxParamsPrefix.Replace(sql, m => _paramPrefix + m.Value.Substring(1)); + sql = sql.Replace("@@", "@"); // <- double @@ escapes a single @ + + // Create the command and add parameters + IDbCommand cmd = connection.CreateCommand(); + cmd.Connection = connection; + cmd.CommandText = sql; + cmd.Transaction = _transaction; + foreach (var item in args) + { + AddParam(cmd, item, _paramPrefix); + } + + if (_dbType == DBType.Oracle) + { + cmd.GetType().GetProperty("BindByName").SetValue(cmd, true, null); + } + + if (!String.IsNullOrEmpty(sql)) + DoPreExecute(cmd); + + return cmd; + } + + // Override this to log/capture exceptions + public virtual void OnException(Exception x) + { + System.Diagnostics.Debug.WriteLine(x.ToString()); + System.Diagnostics.Debug.WriteLine(LastCommand); + } + + // Override this to log commands, or modify command before execution + public virtual IDbConnection OnConnectionOpened(IDbConnection conn) { return conn; } + public virtual void OnConnectionClosing(IDbConnection conn) { } + public virtual void OnExecutingCommand(IDbCommand cmd) { } + public virtual void OnExecutedCommand(IDbCommand cmd) { } + + // Execute a non-query command + public int Execute(string sql, params object[] args) + { + try + { + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, sql, args)) + { + var retv=cmd.ExecuteNonQuery(); + OnExecutedCommand(cmd); + return retv; + } + } + finally + { + CloseSharedConnection(); + } + } + catch (Exception x) + { + OnException(x); + throw; + } + } + + public int Execute(Sql sql) + { + return Execute(sql.SQL, sql.Arguments); + } + + // Execute and cast a scalar property + public T ExecuteScalar(string sql, params object[] args) + { + try + { + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, sql, args)) + { + object val = cmd.ExecuteScalar(); + OnExecutedCommand(cmd); + return (T)Convert.ChangeType(val, typeof(T)); + } + } + finally + { + CloseSharedConnection(); + } + } + catch (Exception x) + { + OnException(x); + throw; + } + } + + public T ExecuteScalar(Sql sql) + { + return ExecuteScalar(sql.SQL, sql.Arguments); + } + + Regex rxSelect = new Regex(@"\A\s*(SELECT|EXECUTE|CALL)\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); + Regex rxFrom = new Regex(@"\A\s*FROM\s", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline); + string AddSelectClause(string sql) + { + if (sql.StartsWith(";")) + return sql.Substring(1); + + if (!rxSelect.IsMatch(sql)) + { + var pd = PocoData.ForType(typeof(T)); + var tableName = EscapeTableName(pd.TableInfo.TableName); + string cols = string.Join(", ", (from c in pd.QueryColumns select tableName + "." + EscapeSqlIdentifier(c)).ToArray()); + if (!rxFrom.IsMatch(sql)) + sql = string.Format("SELECT {0} FROM {1} {2}", cols, tableName, sql); + else + sql = string.Format("SELECT {0} {1}", cols, sql); + } + return sql; + } + + public bool EnableAutoSelect { get; set; } + public bool EnableNamedParams { get; set; } + public bool ForceDateTimesToUtc { get; set; } + + // Return a typed list of pocos + public List Fetch(string sql, params object[] args) + { + return Query(sql, args).ToList(); + } + + public List Fetch(Sql sql) + { + return Fetch(sql.SQL, sql.Arguments); + } + + static Regex rxColumns = new Regex(@"\A\s*SELECT\s+((?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|.)*?)(?\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?(?:\s*,\s*(?:\((?>\((?)|\)(?<-depth>)|.?)*(?(depth)(?!))\)|[\w\(\)\.])+(?:\s+(?:ASC|DESC))?)*", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + static Regex rxDistinct = new Regex(@"\ADISTINCT\s", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.Singleline | RegexOptions.Compiled); + public static bool SplitSqlForPaging(string sql, out string sqlCount, out string sqlSelectRemoved, out string sqlOrderBy) + { + sqlSelectRemoved = null; + sqlCount = null; + sqlOrderBy = null; + + // Extract the columns from "SELECT FROM" + var m = rxColumns.Match(sql); + if (!m.Success) + return false; + + // Save column list and replace with COUNT(*) + Group g = m.Groups[1]; + sqlSelectRemoved = sql.Substring(g.Index); + + if (rxDistinct.IsMatch(sqlSelectRemoved)) + sqlCount = sql.Substring(0, g.Index) + "COUNT(" + m.Groups[1].ToString().Trim() + ") " + sql.Substring(g.Index + g.Length); + else + sqlCount = sql.Substring(0, g.Index) + "COUNT(*) " + sql.Substring(g.Index + g.Length); + + + // Look for an "ORDER BY " clause + m = rxOrderBy.Match(sqlCount); + if (!m.Success) + { + sqlOrderBy = null; + } + else + { + g = m.Groups[0]; + sqlOrderBy = g.ToString(); + sqlCount = sqlCount.Substring(0, g.Index) + sqlCount.Substring(g.Index + g.Length); + } + + return true; + } + + public void BuildPageQueries(long skip, long take, string sql, ref object[] args, out string sqlCount, out string sqlPage) + { + // Add auto select clause + if (EnableAutoSelect) + sql = AddSelectClause(sql); + + // Split the SQL into the bits we need + string sqlSelectRemoved, sqlOrderBy; + if (!SplitSqlForPaging(sql, out sqlCount, out sqlSelectRemoved, out sqlOrderBy)) + throw new Exception("Unable to parse SQL statement for paged query"); + if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) + throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); + + // Build the SQL for the actual final indexList + if (_dbType == DBType.SqlServer || _dbType == DBType.Oracle) + { + sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); + if (rxDistinct.IsMatch(sqlSelectRemoved)) + { + sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; + } + sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", + sqlOrderBy==null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, skip+take }).ToArray(); + } + else if (_dbType == DBType.SqlServerCE) + { + sqlPage = string.Format("{0}\nOFFSET @{1} ROWS FETCH NEXT @{2} ROWS ONLY", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { skip, take }).ToArray(); + } + else + { + sqlPage = string.Format("{0}\nLIMIT @{1} OFFSET @{2}", sql, args.Length, args.Length + 1); + args = args.Concat(new object[] { take, skip }).ToArray(); + } + + } + + // Fetch a page + public Page Page(long page, long itemsPerPage, string sql, params object[] args) + { + string sqlCount, sqlPage; + BuildPageQueries((page-1)*itemsPerPage, itemsPerPage, sql, ref args, out sqlCount, out sqlPage); + + // Save the one-time command time out and use it for both queries + int saveTimeout = OneTimeCommandTimeout; + + // Setup the paged indexList + var result = new Page(); + result.CurrentPage = page; + result.ItemsPerPage = itemsPerPage; + result.TotalItems = ExecuteScalar(sqlCount, args); + result.TotalPages = result.TotalItems / itemsPerPage; + if ((result.TotalItems % itemsPerPage) != 0) + result.TotalPages++; + + OneTimeCommandTimeout = saveTimeout; + + // Get the records + result.Items = Fetch(sqlPage, args); + + // Done + return result; + } + + public Page Page(long page, long itemsPerPage, Sql sql) + { + return Page(page, itemsPerPage, sql.SQL, sql.Arguments); + } + + + public List Fetch(long page, long itemsPerPage, string sql, params object[] args) + { + return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql, args); + } + + public List Fetch(long page, long itemsPerPage, Sql sql) + { + return SkipTake((page - 1) * itemsPerPage, itemsPerPage, sql.SQL, sql.Arguments); + } + + public List SkipTake(long skip, long take, string sql, params object[] args) + { + string sqlCount, sqlPage; + BuildPageQueries(skip, take, sql, ref args, out sqlCount, out sqlPage); + return Fetch(sqlPage, args); + } + + public List SkipTake(long skip, long take, Sql sql) + { + return SkipTake(skip, take, sql.SQL, sql.Arguments); + } + + // Return an enumerable collection of pocos + public IEnumerable Query(string sql, params object[] args) + { + if (EnableAutoSelect) + sql = AddSelectClause(sql); + + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, sql, args)) + { + IDataReader r; + var pd = PocoData.ForType(typeof(T)); + try + { + r = cmd.ExecuteReader(); + OnExecutedCommand(cmd); + } + catch (Exception x) + { + OnException(x); + throw; + } + var factory = pd.GetFactory(cmd.CommandText, _sharedConnection.ConnectionString, ForceDateTimesToUtc, 0, r.FieldCount, r) as Func; + using (r) + { + while (true) + { + T poco; + try + { + if (!r.Read()) + yield break; + poco = factory(r); + } + catch (Exception x) + { + OnException(x); + throw; + } + + yield return poco; + } + } + } + } + finally + { + CloseSharedConnection(); + } + } + + // Multi Fetch + public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } + public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } + public List Fetch(Func cb, string sql, params object[] args) { return Query(cb, sql, args).ToList(); } + + // Multi Query + public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql, args); } + public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3)}, cb, sql, args); } + public IEnumerable Query(Func cb, string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4)}, cb, sql, args); } + + // Multi Fetch (SQL builder) + public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } + public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } + public List Fetch(Func cb, Sql sql) { return Query(cb, sql.SQL, sql.Arguments).ToList(); } + + // Multi Query (SQL builder) + public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, cb, sql.SQL, sql.Arguments); } + public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, cb, sql.SQL, sql.Arguments); } + public IEnumerable Query(Func cb, Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, cb, sql.SQL, sql.Arguments); } + + // Multi Fetch (Simple) + public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } + public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } + public List Fetch(string sql, params object[] args) { return Query(sql, args).ToList(); } + + // Multi Query (Simple) + public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql, args); } + public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql, args); } + public IEnumerable Query(string sql, params object[] args) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql, args); } + + // Multi Fetch (Simple) (SQL builder) + public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } + public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } + public List Fetch(Sql sql) { return Query(sql.SQL, sql.Arguments).ToList(); } + + // Multi Query (Simple) (SQL builder) + public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2) }, null, sql.SQL, sql.Arguments); } + public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3) }, null, sql.SQL, sql.Arguments); } + public IEnumerable Query(Sql sql) { return Query(new Type[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, null, sql.SQL, sql.Arguments); } + + // Automagically guess the property relationships between various POCOs and create a delegate that will set them up + object GetAutoMapper(Type[] types) + { + // Build a key + var kb = new StringBuilder(); + foreach (var t in types) + { + kb.Append(t.ToString()); + kb.Append(":"); + } + var key = kb.ToString(); + + // Check cache + RWLock.EnterReadLock(); + try + { + object mapper; + if (AutoMappers.TryGetValue(key, out mapper)) + return mapper; + } + finally + { + RWLock.ExitReadLock(); + } + + // Create it + RWLock.EnterWriteLock(); + try + { + // Try again + object mapper; + if (AutoMappers.TryGetValue(key, out mapper)) + return mapper; + + // Create a method + var m = new DynamicMethod("petapoco_automapper", types[0], types, true); + var il = m.GetILGenerator(); + + for (int i = 1; i < types.Length; i++) + { + bool handled = false; + for (int j = i - 1; j >= 0; j--) + { + // Find the property + var candidates = from p in types[j].GetProperties() where p.PropertyType == types[i] select p; + if (candidates.Count() == 0) + continue; + if (candidates.Count() > 1) + throw new InvalidOperationException(string.Format("Can't auto join {0} as {1} has more than one property of type {0}", types[i], types[j])); + + // Generate code + il.Emit(OpCodes.Ldarg_S, j); + il.Emit(OpCodes.Ldarg_S, i); + il.Emit(OpCodes.Callvirt, candidates.First().GetSetMethod(true)); + handled = true; + } + + if (!handled) + throw new InvalidOperationException(string.Format("Can't auto join {0}", types[i])); + } + + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ret); + + // Cache it + var del = m.CreateDelegate(Expression.GetFuncType(types.Concat(types.Take(1)).ToArray())); + AutoMappers.Add(key, del); + return del; + } + finally + { + RWLock.ExitWriteLock(); + } + } + + // Find the split point in a indexList set for two different pocos and return the poco factory for the first + Delegate FindSplitPoint(Type typeThis, Type typeNext, string sql, IDataReader r, ref int pos) + { + // Last? + if (typeNext == null) + return PocoData.ForType(typeThis).GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, pos, r.FieldCount - pos, r); + + // Get PocoData for the two types + PocoData pdThis = PocoData.ForType(typeThis); + PocoData pdNext = PocoData.ForType(typeNext); + + // Find split point + int firstColumn = pos; + var usedColumns = new Dictionary(); + for (; pos < r.FieldCount; pos++) + { + // Split if field name has already been used, or if the field doesn't exist in current poco but does in the next + string fieldName = r.GetName(pos); + if (usedColumns.ContainsKey(fieldName) || (!pdThis.Columns.ContainsKey(fieldName) && pdNext.Columns.ContainsKey(fieldName))) + { + return pdThis.GetFactory(sql, _sharedConnection.ConnectionString, ForceDateTimesToUtc, firstColumn, pos - firstColumn, r); + } + usedColumns.Add(fieldName, true); + } + + throw new InvalidOperationException(string.Format("Couldn't find split point between {0} and {1}", typeThis, typeNext)); + } + + // Instance data used by the Multipoco factory delegate - essentially a list of the nested poco factories to call + class MultiPocoFactory + { + public List m_Delegates; + public Delegate GetItem(int index) { return m_Delegates[index]; } + } + + // Create a multi-poco factory + Func CreateMultiPocoFactory(Type[] types, string sql, IDataReader r) + { + var m = new DynamicMethod("petapoco_multipoco_factory", typeof(TRet), new Type[] { typeof(MultiPocoFactory), typeof(IDataReader), typeof(object) }, typeof(MultiPocoFactory)); + var il = m.GetILGenerator(); + + // Load the callback + il.Emit(OpCodes.Ldarg_2); + + // Call each delegate + var dels = new List(); + int pos = 0; + for (int i=0; i)m.CreateDelegate(typeof(Func), new MultiPocoFactory() { m_Delegates = dels }); + } + + // Various cached stuff + static Dictionary MultiPocoFactories = new Dictionary(); + static Dictionary AutoMappers = new Dictionary(); + static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); + + // Get (or create) the multi-poco factory for a query + Func GetMultiPocoFactory(Type[] types, string sql, IDataReader r) + { + // Build a key string (this is crap, should address this at some point) + var kb = new StringBuilder(); + kb.Append(typeof(TRet).ToString()); + kb.Append(":"); + foreach (var t in types) + { + kb.Append(":"); + kb.Append(t.ToString()); + } + kb.Append(":"); kb.Append(_sharedConnection.ConnectionString); + kb.Append(":"); kb.Append(ForceDateTimesToUtc); + kb.Append(":"); kb.Append(sql); + string key = kb.ToString(); + + // Check cache + RWLock.EnterReadLock(); + try + { + object oFactory; + if (MultiPocoFactories.TryGetValue(key, out oFactory)) + return (Func)oFactory; + } + finally + { + RWLock.ExitReadLock(); + } + + // Cache it + RWLock.EnterWriteLock(); + try + { + // Check again + object oFactory; + if (MultiPocoFactories.TryGetValue(key, out oFactory)) + return (Func)oFactory; + + // Create the factory + var Factory = CreateMultiPocoFactory(types, sql, r); + + MultiPocoFactories.Add(key, Factory); + return Factory; + } + finally + { + RWLock.ExitWriteLock(); + } + + } + + // Actual implementation of the multi-poco query + public IEnumerable Query(Type[] types, object cb, string sql, params object[] args) + { + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, sql, args)) + { + IDataReader r; + try + { + r = cmd.ExecuteReader(); + OnExecutedCommand(cmd); + } + catch (Exception x) + { + OnException(x); + throw; + } + var factory = GetMultiPocoFactory(types, sql, r); + if (cb == null) + cb = GetAutoMapper(types.ToArray()); + bool bNeedTerminator=false; + using (r) + { + while (true) + { + TRet poco; + try + { + if (!r.Read()) + break; + poco = factory(r, cb); + } + catch (Exception x) + { + OnException(x); + throw; + } + + if (poco != null) + yield return poco; + else + bNeedTerminator = true; + } + if (bNeedTerminator) + { + var poco = (TRet)(cb as Delegate).DynamicInvoke(new object[types.Length]); + if (poco != null) + yield return poco; + else + yield break; + } + } + } + } + finally + { + CloseSharedConnection(); + } + } + + + public IEnumerable Query(Sql sql) + { + return Query(sql.SQL, sql.Arguments); + } + + public bool Exists(object primaryKey) + { + return FirstOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey) != null; + } + public T Single(object primaryKey) + { + return Single(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); + } + public T SingleOrDefault(object primaryKey) + { + return SingleOrDefault(string.Format("WHERE {0}=@0", EscapeSqlIdentifier(PocoData.ForType(typeof(T)).TableInfo.PrimaryKey)), primaryKey); + } + public T Single(string sql, params object[] args) + { + return Query(sql, args).Single(); + } + public T SingleOrDefault(string sql, params object[] args) + { + return Query(sql, args).SingleOrDefault(); + } + public T First(string sql, params object[] args) + { + return Query(sql, args).First(); + } + public T FirstOrDefault(string sql, params object[] args) + { + return Query(sql, args).FirstOrDefault(); + } + + public T Single(Sql sql) + { + return Query(sql).Single(); + } + public T SingleOrDefault(Sql sql) + { + return Query(sql).SingleOrDefault(); + } + public T First(Sql sql) + { + return Query(sql).First(); + } + public T FirstOrDefault(Sql sql) + { + return Query(sql).FirstOrDefault(); + } + + public string EscapeTableName(string str) + { + // Assume table names with "dot" are already escaped + return str.IndexOf('.') >= 0 ? str : EscapeSqlIdentifier(str); + } + public string EscapeSqlIdentifier(string str) + { + switch (_dbType) + { + case DBType.MySql: + return string.Format("`{0}`", str); + + case DBType.PostgreSQL: + return string.Format("\"{0}\"", str); + + case DBType.Oracle: + return string.Format("\"{0}\"", str.ToUpperInvariant()); + + default: + return string.Format("[{0}]", str); + } + } + + public object Insert(string tableName, string primaryKeyName, object poco) + { + return Insert(tableName, primaryKeyName, true, poco); + } + + // Insert a poco into a table. If the poco has a property with the same name + // as the primary key the id of the new record is assigned to it. Either way, + // the new id is returned. + public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco) + { + try + { + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, "")) + { + var pd = PocoData.ForObject(poco, primaryKeyName); + var names = new List(); + var values = new List(); + var index = 0; + foreach (var i in pd.Columns) + { + // Don't insert indexList columns + if (i.Value.ResultColumn) + continue; + + // Don't insert the primary key (except under oracle where we need bring in the next sequence value) + if (autoIncrement && primaryKeyName != null && string.Compare(i.Key, primaryKeyName, true)==0) + { + if (_dbType == DBType.Oracle && !string.IsNullOrEmpty(pd.TableInfo.SequenceName)) + { + names.Add(i.Key); + values.Add(string.Format("{0}.nextval", pd.TableInfo.SequenceName)); + } + continue; + } + + names.Add(EscapeSqlIdentifier(i.Key)); + values.Add(string.Format("{0}{1}", _paramPrefix, index++)); + AddParam(cmd, i.Value.GetValue(poco), _paramPrefix); + } + + cmd.CommandText = string.Format("INSERT INTO {0} ({1}) VALUES ({2})", + EscapeTableName(tableName), + string.Join(",", names.ToArray()), + string.Join(",", values.ToArray()) + ); + + if (!autoIncrement) + { + DoPreExecute(cmd); + cmd.ExecuteNonQuery(); + OnExecutedCommand(cmd); + return true; + } + + + object id; + switch (_dbType) + { + case DBType.SqlServerCE: + DoPreExecute(cmd); + cmd.ExecuteNonQuery(); + OnExecutedCommand(cmd); + id = ExecuteScalar("SELECT @@@IDENTITY AS NewID;"); + break; + case DBType.SqlServer: + cmd.CommandText += ";\nSELECT SCOPE_IDENTITY() AS NewID;"; + DoPreExecute(cmd); + id = cmd.ExecuteScalar(); + OnExecutedCommand(cmd); + break; + case DBType.PostgreSQL: + if (primaryKeyName != null) + { + cmd.CommandText += string.Format("returning {0} as NewID", EscapeSqlIdentifier(primaryKeyName)); + DoPreExecute(cmd); + id = cmd.ExecuteScalar(); + } + else + { + id = -1; + DoPreExecute(cmd); + cmd.ExecuteNonQuery(); + } + OnExecutedCommand(cmd); + break; + case DBType.Oracle: + if (primaryKeyName != null) + { + cmd.CommandText += string.Format(" returning {0} into :newid", EscapeSqlIdentifier(primaryKeyName)); + var param = cmd.CreateParameter(); + param.ParameterName = ":newid"; + param.Value = DBNull.Value; + param.Direction = ParameterDirection.ReturnValue; + param.DbType = DbType.Int64; + cmd.Parameters.Add(param); + DoPreExecute(cmd); + cmd.ExecuteNonQuery(); + id = param.Value; + } + else + { + id = -1; + DoPreExecute(cmd); + cmd.ExecuteNonQuery(); + } + OnExecutedCommand(cmd); + break; + case DBType.SQLite: + if (primaryKeyName != null) + { + cmd.CommandText += ";\nSELECT last_insert_rowid();"; + DoPreExecute(cmd); + id = cmd.ExecuteScalar(); + } + else + { + id = -1; + DoPreExecute(cmd); + cmd.ExecuteNonQuery(); + } + OnExecutedCommand(cmd); + break; + default: + cmd.CommandText += ";\nSELECT @@IDENTITY AS NewID;"; + DoPreExecute(cmd); + id = cmd.ExecuteScalar(); + OnExecutedCommand(cmd); + break; + } + + + // Assign the ID back to the primary key property + if (primaryKeyName != null) + { + PocoColumn pc; + if (pd.Columns.TryGetValue(primaryKeyName, out pc)) + { + pc.SetValue(poco, pc.ChangeType(id)); + } + } + + return id; + } + } + finally + { + CloseSharedConnection(); + } + } + catch (Exception x) + { + OnException(x); + throw; + } + } + + // Insert an annotated poco object + public object Insert(object poco) + { + var pd = PocoData.ForType(poco.GetType()); + return Insert(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, pd.TableInfo.AutoIncrement, poco); + } + + public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue) + { + return Update(tableName, primaryKeyName, poco, primaryKeyValue, null); + } + + + // Update a record with values from a poco. primary key value can be either supplied or read from the poco + public int Update(string tableName, string primaryKeyName, object poco, object primaryKeyValue, IEnumerable columns) + { + try + { + OpenSharedConnection(); + try + { + using (var cmd = CreateCommand(_sharedConnection, "")) + { + var sb = new StringBuilder(); + var index = 0; + var pd = PocoData.ForObject(poco,primaryKeyName); + if (columns == null) + { + foreach (var i in pd.Columns) + { + // Don't update the primary key, but grab the value if we don't have it + if (string.Compare(i.Key, primaryKeyName, true) == 0) + { + if (primaryKeyValue == null) + primaryKeyValue = i.Value.GetValue(poco); + continue; + } + + // Dont update indexList only columns + if (i.Value.ResultColumn) + continue; + + // Build the sql + if (index > 0) + sb.Append(", "); + sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(i.Key), _paramPrefix, index++); + + // Store the parameter in the command + AddParam(cmd, i.Value.GetValue(poco), _paramPrefix); + } + } + else + { + foreach (var colname in columns) + { + var pc = pd.Columns[colname]; + + // Build the sql + if (index > 0) + sb.Append(", "); + sb.AppendFormat("{0} = {1}{2}", EscapeSqlIdentifier(colname), _paramPrefix, index++); + + // Store the parameter in the command + AddParam(cmd, pc.GetValue(poco), _paramPrefix); + } + + // Grab primary key value + if (primaryKeyValue == null) + { + var pc = pd.Columns[primaryKeyName]; + primaryKeyValue = pc.GetValue(poco); + } + + } + + cmd.CommandText = string.Format("UPDATE {0} SET {1} WHERE {2} = {3}{4}", + EscapeTableName(tableName), sb.ToString(), EscapeSqlIdentifier(primaryKeyName), _paramPrefix, index++); + AddParam(cmd, primaryKeyValue, _paramPrefix); + + DoPreExecute(cmd); + + // Do it + var retv=cmd.ExecuteNonQuery(); + OnExecutedCommand(cmd); + return retv; + } + } + finally + { + CloseSharedConnection(); + } + } + catch (Exception x) + { + OnException(x); + throw; + } + } + + public int Update(string tableName, string primaryKeyName, object poco) + { + return Update(tableName, primaryKeyName, poco, null); + } + + public int Update(string tableName, string primaryKeyName, object poco, IEnumerable columns) + { + return Update(tableName, primaryKeyName, poco, null, columns); + } + + public int Update(object poco, IEnumerable columns) + { + return Update(poco, null, columns); + } + + public int Update(object poco) + { + return Update(poco, null, null); + } + + public int Update(object poco, object primaryKeyValue) + { + return Update(poco, primaryKeyValue, null); + } + public int Update(object poco, object primaryKeyValue, IEnumerable columns) + { + var pd = PocoData.ForType(poco.GetType()); + return Update(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco, primaryKeyValue, columns); + } + + public int Update(string sql, params object[] args) + { + var pd = PocoData.ForType(typeof(T)); + return Execute(string.Format("UPDATE {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args); + } + + public int Update(Sql sql) + { + var pd = PocoData.ForType(typeof(T)); + return Execute(new Sql(string.Format("UPDATE {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql)); + } + + public int Delete(string tableName, string primaryKeyName, object poco) + { + return Delete(tableName, primaryKeyName, poco, null); + } + + public int Delete(string tableName, string primaryKeyName, object poco, object primaryKeyValue) + { + // If primary key value not specified, pick it up from the object + if (primaryKeyValue == null) + { + var pd = PocoData.ForObject(poco,primaryKeyName); + PocoColumn pc; + if (pd.Columns.TryGetValue(primaryKeyName, out pc)) + { + primaryKeyValue = pc.GetValue(poco); + } + } + + // Do it + var sql = string.Format("DELETE FROM {0} WHERE {1}=@0", EscapeTableName(tableName), EscapeSqlIdentifier(primaryKeyName)); + return Execute(sql, primaryKeyValue); + } + + public int Delete(object poco) + { + var pd = PocoData.ForType(poco.GetType()); + return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); + } + + public int Delete(object pocoOrPrimaryKey) + { + if (pocoOrPrimaryKey.GetType() == typeof(T)) + return Delete(pocoOrPrimaryKey); + var pd = PocoData.ForType(typeof(T)); + return Delete(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, null, pocoOrPrimaryKey); + } + + public int Delete(string sql, params object[] args) + { + var pd = PocoData.ForType(typeof(T)); + return Execute(string.Format("DELETE FROM {0} {1}", EscapeTableName(pd.TableInfo.TableName), sql), args); + } + + public int Delete(Sql sql) + { + var pd = PocoData.ForType(typeof(T)); + return Execute(new Sql(string.Format("DELETE FROM {0}", EscapeTableName(pd.TableInfo.TableName))).Append(sql)); + } + + // Check if a poco represents a new record + public bool IsNew(string primaryKeyName, object poco) + { + var pd = PocoData.ForObject(poco, primaryKeyName); + object pk; + PocoColumn pc; + if (pd.Columns.TryGetValue(primaryKeyName, out pc)) + { + pk = pc.GetValue(poco); + } +#if !PETAPOCO_NO_DYNAMIC + else if (poco.GetType() == typeof(System.Dynamic.ExpandoObject)) + { + return true; + } +#endif + else + { + var pi = poco.GetType().GetProperty(primaryKeyName); + if (pi == null) + throw new ArgumentException(string.Format("The object doesn't have a property matching the primary key column name '{0}'", primaryKeyName)); + pk = pi.GetValue(poco, null); + } + + if (pk == null) + return true; + + var type = pk.GetType(); + + if (type.IsValueType) + { + // Common primary key types + if (type == typeof(long)) + return (long)pk == 0; + else if (type == typeof(ulong)) + return (ulong)pk == 0; + else if (type == typeof(int)) + return (int)pk == 0; + else if (type == typeof(uint)) + return (uint)pk == 0; + + // Create a default instance and compare + return pk == Activator.CreateInstance(pk.GetType()); + } + else + { + return pk == null; + } + } + + public bool IsNew(object poco) + { + var pd = PocoData.ForType(poco.GetType()); + if (!pd.TableInfo.AutoIncrement) + throw new InvalidOperationException("IsNew() and Save() are only supported on tables with auto-increment/identity primary key columns"); + return IsNew(pd.TableInfo.PrimaryKey, poco); + } + + // Insert new record or Update existing record + public void Save(string tableName, string primaryKeyName, object poco) + { + if (IsNew(primaryKeyName, poco)) + { + Insert(tableName, primaryKeyName, true, poco); + } + else + { + Update(tableName, primaryKeyName, poco); + } + } + + public void Save(object poco) + { + var pd = PocoData.ForType(poco.GetType()); + Save(pd.TableInfo.TableName, pd.TableInfo.PrimaryKey, poco); + } + + public int CommandTimeout { get; set; } + public int OneTimeCommandTimeout { get; set; } + + void DoPreExecute(IDbCommand cmd) + { + // Setup command timeout + if (OneTimeCommandTimeout != 0) + { + cmd.CommandTimeout = OneTimeCommandTimeout; + OneTimeCommandTimeout = 0; + } + else if (CommandTimeout!=0) + { + cmd.CommandTimeout = CommandTimeout; + } + + // Call hook + OnExecutingCommand(cmd); + + // Save it + _lastSql = cmd.CommandText; + _lastArgs = (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray(); + } + + public string LastSQL { get { return _lastSql; } } + public object[] LastArgs { get { return _lastArgs; } } + public string LastCommand + { + get { return FormatCommand(_lastSql, _lastArgs); } + } + + public string FormatCommand(IDbCommand cmd) + { + return FormatCommand(cmd.CommandText, (from IDataParameter parameter in cmd.Parameters select parameter.Value).ToArray()); + } + + public string FormatCommand(string sql, object[] args) + { + var sb = new StringBuilder(); + if (sql == null) + return ""; + sb.Append(sql); + if (args != null && args.Length > 0) + { + sb.Append("\n"); + for (int i = 0; i < args.Length; i++) + { + sb.AppendFormat("\t -> {0}{1} [{2}] = \"{3}\"\n", _paramPrefix, i, args[i].GetType().Name, args[i]); + } + sb.Remove(sb.Length - 1, 1); + } + return sb.ToString(); + } + + + public static IMapper Mapper + { + get; + set; + } + + public class PocoColumn + { + public string ColumnName; + public PropertyInfo PropertyInfo; + public bool ResultColumn; + public virtual void SetValue(object target, object val) { PropertyInfo.SetValue(target, val, null); } + public virtual object GetValue(object target) { return PropertyInfo.GetValue(target, null); } + public virtual object ChangeType(object val) { return Convert.ChangeType(val, PropertyInfo.PropertyType); } + } + public class ExpandoColumn : PocoColumn + { + public override void SetValue(object target, object val) { (target as IDictionary)[ColumnName]=val; } + public override object GetValue(object target) + { + object val=null; + (target as IDictionary).TryGetValue(ColumnName, out val); + return val; + } + public override object ChangeType(object val) { return val; } + } + public class PocoData + { + public static PocoData ForObject(object o, string primaryKeyName) + { + var t = o.GetType(); +#if !PETAPOCO_NO_DYNAMIC + if (t == typeof(System.Dynamic.ExpandoObject)) + { + var pd = new PocoData(); + pd.TableInfo = new TableInfo(); + pd.Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + pd.Columns.Add(primaryKeyName, new ExpandoColumn() { ColumnName = primaryKeyName }); + pd.TableInfo.PrimaryKey = primaryKeyName; + pd.TableInfo.AutoIncrement = true; + foreach (var col in (o as IDictionary).Keys) + { + if (col!=primaryKeyName) + pd.Columns.Add(col, new ExpandoColumn() { ColumnName = col }); + } + return pd; + } + else +#endif + return ForType(t); + } + static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim(); + public static PocoData ForType(Type t) + { +#if !PETAPOCO_NO_DYNAMIC + if (t == typeof(System.Dynamic.ExpandoObject)) + throw new InvalidOperationException("Can't use dynamic types with this method"); +#endif + // Check cache + RWLock.EnterReadLock(); + PocoData pd; + try + { + if (m_PocoDatas.TryGetValue(t, out pd)) + return pd; + } + finally + { + RWLock.ExitReadLock(); + } + + + // Cache it + RWLock.EnterWriteLock(); + try + { + // Check again + if (m_PocoDatas.TryGetValue(t, out pd)) + return pd; + + // Create it + pd = new PocoData(t); + + m_PocoDatas.Add(t, pd); + } + finally + { + RWLock.ExitWriteLock(); + } + + return pd; + } + + public PocoData() + { + } + + public PocoData(Type t) + { + type = t; + TableInfo=new TableInfo(); + + // Get the table name + var a = t.GetCustomAttributes(typeof(TableNameAttribute), true); + TableInfo.TableName = a.Length == 0 ? t.Name : (a[0] as TableNameAttribute).Value; + + // Get the primary key + a = t.GetCustomAttributes(typeof(PrimaryKeyAttribute), true); + TableInfo.PrimaryKey = a.Length == 0 ? "ID" : (a[0] as PrimaryKeyAttribute).Value; + TableInfo.SequenceName = a.Length == 0 ? null : (a[0] as PrimaryKeyAttribute).sequenceName; + TableInfo.AutoIncrement = a.Length == 0 ? false : (a[0] as PrimaryKeyAttribute).autoIncrement; + + // Call column mapper + if (Database.Mapper != null) + Database.Mapper.GetTableInfo(t, TableInfo); + + // Work out bound properties + bool ExplicitColumns = t.GetCustomAttributes(typeof(ExplicitColumnsAttribute), true).Length > 0; + Columns = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var pi in t.GetProperties()) + { + // Work out if properties is to be included + var ColAttrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true); + if (ExplicitColumns) + { + if (ColAttrs.Length == 0) + continue; + } + else + { + if (pi.GetCustomAttributes(typeof(IgnoreAttribute), true).Length != 0) + continue; + } + + var pc = new PocoColumn(); + pc.PropertyInfo = pi; + + // Work out the DB column name + if (ColAttrs.Length > 0) + { + var colattr = (ColumnAttribute)ColAttrs[0]; + pc.ColumnName = colattr.Name; + if ((colattr as ResultColumnAttribute) != null) + pc.ResultColumn = true; + } + if (pc.ColumnName == null) + { + pc.ColumnName = pi.Name; + if (Database.Mapper != null && !Database.Mapper.MapPropertyToColumn(pi, ref pc.ColumnName, ref pc.ResultColumn)) + continue; + } + + // Store it + Columns.Add(pc.ColumnName, pc); + } + + // Build column list for automatic select + QueryColumns = (from c in Columns where !c.Value.ResultColumn select c.Key).ToArray(); + + } + + static bool IsIntegralType(Type t) + { + var tc = Type.GetTypeCode(t); + return tc >= TypeCode.SByte && tc <= TypeCode.UInt64; + } + + // Create factory function that can convert a IDataReader record into a POCO + public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r) + { + // Check cache + var key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns); + RWLock.EnterReadLock(); + try + { + // Have we already created it? + Delegate factory; + if (PocoFactories.TryGetValue(key, out factory)) + return factory; + } + finally + { + RWLock.ExitReadLock(); + } + + // Take the writer lock + RWLock.EnterWriteLock(); + + try + { + + // Check again, just in case + Delegate factory; + if (PocoFactories.TryGetValue(key, out factory)) + return factory; + + // Create the method + var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader) }, true); + var il = m.GetILGenerator(); + +#if !PETAPOCO_NO_DYNAMIC + if (type == typeof(object)) + { + // var poco=new T() + il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj + + MethodInfo fnAdd = typeof(IDictionary).GetMethod("Add"); + + // Enumerate all fields generating a set assignment for the column + for (int i = firstColumn; i < firstColumn + countColumns; i++) + { + var srcType = r.GetFieldType(i); + + il.Emit(OpCodes.Dup); // obj, obj + il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname + + // Get the converter + Func converter = null; + if (Database.Mapper != null) + converter = Database.Mapper.GetFromDbConverter(null, srcType); + if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime)) + converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; + + // Setup stack for call to converter + AddConverterToStack(il, converter); + + // r[i] + il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr + il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i + il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value + + // Convert DBNull to null + il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value + il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null) + var lblNotNull = il.DefineLabel(); + il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value + il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter? + if (converter != null) + il.Emit(OpCodes.Pop); // obj, obj, fieldname, + il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null + if (converter != null) + { + var lblReady = il.DefineLabel(); + il.Emit(OpCodes.Br_S, lblReady); + il.MarkLabel(lblNotNull); + il.Emit(OpCodes.Callvirt, fnInvoke); + il.MarkLabel(lblReady); + } + else + { + il.MarkLabel(lblNotNull); + } + + il.Emit(OpCodes.Callvirt, fnAdd); + } + } + else +#endif + if (type.IsValueType || type == typeof(string) || type == typeof(byte[])) + { + // Do we need to install a converter? + var srcType = r.GetFieldType(0); + var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type); + + // "if (!rdr.IsDBNull(i))" + il.Emit(OpCodes.Ldarg_0); // rdr + il.Emit(OpCodes.Ldc_I4_0); // rdr,0 + il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool + var lblCont = il.DefineLabel(); + il.Emit(OpCodes.Brfalse_S, lblCont); + il.Emit(OpCodes.Ldnull); // null + var lblFin = il.DefineLabel(); + il.Emit(OpCodes.Br_S, lblFin); + + il.MarkLabel(lblCont); + + // Setup stack for call to converter + AddConverterToStack(il, converter); + + il.Emit(OpCodes.Ldarg_0); // rdr + il.Emit(OpCodes.Ldc_I4_0); // rdr,0 + il.Emit(OpCodes.Callvirt, fnGetValue); // value + + // Call the converter + if (converter != null) + il.Emit(OpCodes.Callvirt, fnInvoke); + + il.MarkLabel(lblFin); + il.Emit(OpCodes.Unbox_Any, type); // value converted + } + else + { + // var poco=new T() + il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); + + // Enumerate all fields generating a set assignment for the column + for (int i = firstColumn; i < firstColumn + countColumns; i++) + { + // Get the PocoColumn for this db column, ignore if not known + PocoColumn pc; + if (!Columns.TryGetValue(r.GetName(i), out pc)) + continue; + + // Get the source type for this column + var srcType = r.GetFieldType(i); + var dstType = pc.PropertyInfo.PropertyType; + + // "if (!rdr.IsDBNull(i))" + il.Emit(OpCodes.Ldarg_0); // poco,rdr + il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i + il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool + var lblNext = il.DefineLabel(); + il.Emit(OpCodes.Brtrue_S, lblNext); // poco + + il.Emit(OpCodes.Dup); // poco,poco + + // Do we need to install a converter? + var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType); + + // Fast + bool Handled = false; + if (converter == null) + { + var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) }); + if (valuegetter != null + && valuegetter.ReturnType == srcType + && (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType))) + { + il.Emit(OpCodes.Ldarg_0); // *,rdr + il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i + il.Emit(OpCodes.Callvirt, valuegetter); // *,value + + // Convert to Nullable + if (Nullable.GetUnderlyingType(dstType) != null) + { + il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) })); + } + + il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco + Handled = true; + } + } + + // Not so fast + if (!Handled) + { + // Setup stack for call to converter + AddConverterToStack(il, converter); + + // "value = rdr.GetValue(i)" + il.Emit(OpCodes.Ldarg_0); // *,rdr + il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i + il.Emit(OpCodes.Callvirt, fnGetValue); // *,value + + // Call the converter + if (converter != null) + il.Emit(OpCodes.Callvirt, fnInvoke); + + // Assign it + il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value + il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco + } + + il.MarkLabel(lblNext); + } + + var fnOnLoaded = RecurseInheritedTypes(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null)); + if (fnOnLoaded != null) + { + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Callvirt, fnOnLoaded); + } + } + + il.Emit(OpCodes.Ret); + + // Cache it, return it + var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type)); + PocoFactories.Add(key, del); + return del; + } + finally + { + RWLock.ExitWriteLock(); + } + } + + private static void AddConverterToStack(ILGenerator il, Func converter) + { + if (converter != null) + { + // Add the converter + int converterIndex = m_Converters.Count; + m_Converters.Add(converter); + + // Generate IL to push the converter onto the stack + il.Emit(OpCodes.Ldsfld, fldConverters); + il.Emit(OpCodes.Ldc_I4, converterIndex); + il.Emit(OpCodes.Callvirt, fnListGetItem); // Converter + } + } + + private static Func GetConverter(bool forceDateTimesToUtc, PocoColumn pc, Type srcType, Type dstType) + { + Func converter = null; + + // Get converter from the mapper + if (Database.Mapper != null) + { + if (pc != null) + { + converter = Database.Mapper.GetFromDbConverter(pc.PropertyInfo, srcType); + } + else + { + var m2 = Database.Mapper as IMapper2; + if (m2 != null) + { + converter = m2.GetFromDbConverter(dstType, srcType); + } + } + } + + // Standard DateTime->Utc mapper + if (forceDateTimesToUtc && converter == null && srcType == typeof(DateTime) && (dstType == typeof(DateTime) || dstType == typeof(DateTime?))) + { + converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); }; + } + + // Forced type conversion including integral types -> enum + if (converter == null) + { + if (dstType.IsEnum && IsIntegralType(srcType)) + { + if (srcType != typeof(int)) + { + converter = delegate(object src) { return Convert.ChangeType(src, typeof(int), null); }; + } + } + else if (!dstType.IsAssignableFrom(srcType)) + { + converter = delegate(object src) { return Convert.ChangeType(src, dstType, null); }; + } + } + return converter; + } + + + static T RecurseInheritedTypes(Type t, Func cb) + { + while (t != null) + { + T info = cb(t); + if (info != null) + return info; + t = t.BaseType; + } + return default(T); + } + + + static Dictionary m_PocoDatas = new Dictionary(); + static List> m_Converters = new List>(); + static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) }); + static MethodInfo fnIsDBNull = typeof(IDataRecord).GetMethod("IsDBNull"); + static FieldInfo fldConverters = typeof(PocoData).GetField("m_Converters", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic); + static MethodInfo fnListGetItem = typeof(List>).GetProperty("Item").GetGetMethod(); + static MethodInfo fnInvoke = typeof(Func).GetMethod("Invoke"); + public Type type; + public string[] QueryColumns { get; private set; } + public TableInfo TableInfo { get; private set; } + public Dictionary Columns { get; private set; } + Dictionary PocoFactories = new Dictionary(); + } + + + // Member variables + string _connectionString; + string _providerName; + DbProviderFactory _factory; + IDbConnection _sharedConnection; + IDbTransaction _transaction; + int _sharedConnectionDepth; + int _transactionDepth; + bool _transactionCancelled; + string _lastSql; + object[] _lastArgs; + string _paramPrefix = "@"; + } + + // Transaction object helps maintain transaction depth counts + public class Transaction : IDisposable + { + public Transaction(Database db) + { + _db = db; + _db.BeginTransaction(); + } + + public virtual void Complete() + { + _db.CompleteTransaction(); + _db = null; + } + + public void Dispose() + { + if (_db != null) + _db.AbortTransaction(); + } + + Database _db; + } + + // Simple helper class for building SQL statments + public class Sql + { + public Sql() + { + } + + public Sql(string sql, params object[] args) + { + _sql = sql; + _args = args; + } + + public static Sql Builder + { + get { return new Sql(); } + } + + string _sql; + object[] _args; + Sql _rhs; + string _sqlFinal; + object[] _argsFinal; + + private void Build() + { + // already built? + if (_sqlFinal != null) + return; + + // Build it + var sb = new StringBuilder(); + var args = new List(); + Build(sb, args, null); + _sqlFinal = sb.ToString(); + _argsFinal = args.ToArray(); + } + + public string SQL + { + get + { + Build(); + return _sqlFinal; + } + } + + public object[] Arguments + { + get + { + Build(); + return _argsFinal; + } + } + + public Sql Append(Sql sql) + { + if (_rhs != null) + _rhs.Append(sql); + else + _rhs = sql; + + return this; + } + + public Sql Append(string sql, params object[] args) + { + return Append(new Sql(sql, args)); + } + + static bool Is(Sql sql, string sqltype) + { + return sql != null && sql._sql != null && sql._sql.StartsWith(sqltype, StringComparison.InvariantCultureIgnoreCase); + } + + private void Build(StringBuilder sb, List args, Sql lhs) + { + if (!String.IsNullOrEmpty(_sql)) + { + // Add SQL to the string + if (sb.Length > 0) + { + sb.Append("\n"); + } + + var sql = Database.ProcessParams(_sql, _args, args); + + if (Is(lhs, "WHERE ") && Is(this, "WHERE ")) + sql = "AND " + sql.Substring(6); + if (Is(lhs, "ORDER BY ") && Is(this, "ORDER BY ")) + sql = ", " + sql.Substring(9); + + sb.Append(sql); + } + + // Now do rhs + if (_rhs != null) + _rhs.Build(sb, args, this); + } + + public Sql Where(string sql, params object[] args) + { + return Append(new Sql("WHERE (" + sql + ")", args)); + } + + public Sql OrderBy(params object[] columns) + { + return Append(new Sql("ORDER BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); + } + + public Sql Select(params object[] columns) + { + return Append(new Sql("SELECT " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); + } + + public Sql From(params object[] tables) + { + return Append(new Sql("FROM " + String.Join(", ", (from x in tables select x.ToString()).ToArray()))); + } + + public Sql GroupBy(params object[] columns) + { + return Append(new Sql("GROUP BY " + String.Join(", ", (from x in columns select x.ToString()).ToArray()))); + } + + private SqlJoinClause Join(string JoinType, string table) + { + return new SqlJoinClause(Append(new Sql(JoinType + table))); + } + + public SqlJoinClause InnerJoin(string table) { return Join("INNER JOIN ", table); } + public SqlJoinClause LeftJoin(string table) { return Join("LEFT JOIN ", table); } + + public class SqlJoinClause + { + private readonly Sql _sql; + + public SqlJoinClause(Sql sql) + { + _sql = sql; + } + + public Sql On(string onClause, params object[] args) + { + return _sql.Append("ON " + onClause, args); + } + } + } + +} diff --git a/SqlServerDatabaseDocumentationGenerator/third-party/bootstrap/bootstrap.min.css b/SqlServerDatabaseDocumentationGenerator/third-party/bootstrap/bootstrap.min.css new file mode 100644 index 0000000..381834e --- /dev/null +++ b/SqlServerDatabaseDocumentationGenerator/third-party/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.1.0 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}.list-inline>li:first-child{padding-left:0}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.428571429}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.428571429;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.428571429}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666666666666%}.col-xs-10{width:83.33333333333334%}.col-xs-9{width:75%}.col-xs-8{width:66.66666666666666%}.col-xs-7{width:58.333333333333336%}.col-xs-6{width:50%}.col-xs-5{width:41.66666666666667%}.col-xs-4{width:33.33333333333333%}.col-xs-3{width:25%}.col-xs-2{width:16.666666666666664%}.col-xs-1{width:8.333333333333332%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666666666666%}.col-xs-pull-10{right:83.33333333333334%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666666666666%}.col-xs-pull-7{right:58.333333333333336%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666666666667%}.col-xs-pull-4{right:33.33333333333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.666666666666664%}.col-xs-pull-1{right:8.333333333333332%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666666666666%}.col-xs-push-10{left:83.33333333333334%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666666666666%}.col-xs-push-7{left:58.333333333333336%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666666666667%}.col-xs-push-4{left:33.33333333333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.666666666666664%}.col-xs-push-1{left:8.333333333333332%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666666666666%}.col-xs-offset-10{margin-left:83.33333333333334%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666666666666%}.col-xs-offset-7{margin-left:58.333333333333336%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666666666667%}.col-xs-offset-4{margin-left:33.33333333333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.666666666666664%}.col-xs-offset-1{margin-left:8.333333333333332%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666666666666%}.col-sm-10{width:83.33333333333334%}.col-sm-9{width:75%}.col-sm-8{width:66.66666666666666%}.col-sm-7{width:58.333333333333336%}.col-sm-6{width:50%}.col-sm-5{width:41.66666666666667%}.col-sm-4{width:33.33333333333333%}.col-sm-3{width:25%}.col-sm-2{width:16.666666666666664%}.col-sm-1{width:8.333333333333332%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666666666666%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666666666666%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666666666666%}.col-md-10{width:83.33333333333334%}.col-md-9{width:75%}.col-md-8{width:66.66666666666666%}.col-md-7{width:58.333333333333336%}.col-md-6{width:50%}.col-md-5{width:41.66666666666667%}.col-md-4{width:33.33333333333333%}.col-md-3{width:25%}.col-md-2{width:16.666666666666664%}.col-md-1{width:8.333333333333332%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666666666666%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666666666666%}.col-md-push-10{left:83.33333333333334%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666666666666%}.col-md-push-7{left:58.333333333333336%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666666666667%}.col-md-push-4{left:33.33333333333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.666666666666664%}.col-md-push-1{left:8.333333333333332%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666666666666%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666666666666%}.col-lg-10{width:83.33333333333334%}.col-lg-9{width:75%}.col-lg-8{width:66.66666666666666%}.col-lg-7{width:58.333333333333336%}.col-lg-6{width:50%}.col-lg-5{width:41.66666666666667%}.col-lg-4{width:33.33333333333333%}.col-lg-3{width:25%}.col-lg-2{width:16.666666666666664%}.col-lg-1{width:8.333333333333332%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666666666666%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666666666666%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.428571429;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=date]{line-height:34px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.has-feedback .form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.428571429;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:400;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.428571429;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:gray}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{display:block;max-width:100%;height:auto;margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group .list-group-item:first-child{border-top:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel>.list-group:first-child .list-group-item:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tfoot>tr:first-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tfoot>tr:first-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:first-child>td{border-top:0}.panel>.table-bordered>thead>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:last-child>th,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:last-child>td,.panel>.table-responsive>.table-bordered>thead>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#faebcc}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ebccd1}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.428571429px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top .arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right .arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom .arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left .arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5) 0),color-stop(rgba(0,0,0,.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.0001) 0),color-stop(rgba(0,0,0,.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicons-chevron-left,.carousel-control .glyphicons-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,tr.visible-xs,th.visible-xs,td.visible-xs{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}.visible-sm,tr.visible-sm,th.visible-sm,td.visible-sm{display:none!important}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}.visible-md,tr.visible-md,th.visible-md,td.visible-md{display:none!important}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}.visible-lg,tr.visible-lg,th.visible-lg,td.visible-lg{display:none!important}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (max-width:767px){.hidden-xs,tr.hidden-xs,th.hidden-xs,td.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm,tr.hidden-sm,th.hidden-sm,td.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md,tr.hidden-md,th.hidden-md,td.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg,tr.hidden-lg,th.hidden-lg,td.hidden-lg{display:none!important}}.visible-print,tr.visible-print,th.visible-print,td.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}@media print{.hidden-print,tr.hidden-print,th.hidden-print,td.hidden-print{display:none!important}} \ No newline at end of file diff --git a/SqlServerDatabaseDocumentationGeneratorTest/Properties/AssemblyInfo.cs b/SqlServerDatabaseDocumentationGeneratorTest/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f1b2b87 --- /dev/null +++ b/SqlServerDatabaseDocumentationGeneratorTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SqlServerDatabaseDocumentationGeneratorTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("SqlServerDatabaseDocumentationGeneratorTest")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9f405998-ae6e-4b18-85ee-9397c1eae097")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SqlServerDatabaseDocumentationGeneratorTest/SqlServerDatabaseDocumentationGeneratorTest.csproj b/SqlServerDatabaseDocumentationGeneratorTest/SqlServerDatabaseDocumentationGeneratorTest.csproj new file mode 100644 index 0000000..db45f12 --- /dev/null +++ b/SqlServerDatabaseDocumentationGeneratorTest/SqlServerDatabaseDocumentationGeneratorTest.csproj @@ -0,0 +1,23 @@ + + + + + net48 + Library + SqlServerDatabaseDocumentationGeneratorTest + SqlServerDatabaseDocumentationGeneratorTest + true + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/SqlServerDatabaseDocumentationGeneratorTest/TestExtendedPropertyExtension.cs b/SqlServerDatabaseDocumentationGeneratorTest/TestExtendedPropertyExtension.cs new file mode 100644 index 0000000..5dabf0a --- /dev/null +++ b/SqlServerDatabaseDocumentationGeneratorTest/TestExtendedPropertyExtension.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Model; +using net.datacowboy.SqlServerDatabaseDocumentationGenerator.Utility; + +namespace SqlServerDatabaseDocumentationGeneratorTest +{ + [TestClass] + public class TestExtendedPropertyExtension + { + + private Database getTestDatabase() + { + var db = new Database { DatabaseName = "TEST", Description = "OLTP for tests", ObjectId = 0, Parent = null, Schemas = null }; + + var schema = new Schema { SchemaId = 1, SchemaName = "Sales", Description = "Sales department", Parent = db }; + db.Schemas = new List(); + db.Schemas.Add(schema); + + var view = new View { ViewId = 20, ViewName = "vTest", Description = "Sample view", Parent = schema }; + schema.Views = new List(); + schema.Views.Add(view); + + return db; + } + + [TestMethod] + public void TestCreateDescriptionSqlCommandTextForDatabase() + { + var db = new Database { DatabaseName = "TEST", Description = "OLTP for tests", ObjectId = 0, Parent = null, Schemas = null }; + + string actual = db.CreateDescriptionSqlCommandText(); + string expected = "EXEC sp_addextendedproperty @name='MS_Description' , @value='OLTP for tests';"; + + Assert.AreEqual(actual, expected, "TestCreateDescriptionSqlCommandTextForDatabase failed"); + + } + + [TestMethod] + public void TestCreateDescriptionSqlCommandTextForSchema() + { + var db = this.getTestDatabase(); + var schema = db.Schemas[0]; + + string actual = schema.CreateDescriptionSqlCommandText(); + string expected = "EXEC sp_addextendedproperty @name='MS_Description' , @value='Sales department' , @level0type='SCHEMA' , @level0name='Sales';"; + + Assert.AreEqual(actual, expected, "TestCreateDescriptionSqlCommandTextForSchema failed"); + + } + + [TestMethod] + public void TestCreateDescriptionSqlCommandTextForView() + { + var db = this.getTestDatabase(); + var view = db.Schemas[0].Views[0]; + + string actual = view.CreateDescriptionSqlCommandText(); + string expected = "EXEC sp_addextendedproperty @name='MS_Description' , @value='Sample view' , @level0type='SCHEMA' , @level0name='Sales' , @level1type='VIEW' , @level1name='vTest';"; + + Assert.AreEqual(actual, expected, "TestCreateDescriptionSqlCommandTextForView failed"); + + } + } +}