If you run the following in sql server...
CREATE SCHEMA [cp]
GO
CREATE TABLE [cp].[TestIt](
[ID] [int] NULL
) ON [PRIMARY]
GO
CREATE PROCEDURE cp.ProcSub
AS
BEGIN
Print 'Proc Sub'
END
GO
CREATE PROCEDURE cp.ProcMain
AS
BEGIN
Print 'Proc Main'
EXEC ProcSub
END
GO
CREATE PROCEDURE cp.ProcMain2
AS
BEGIN
Print 'Proc Main2'
SELECT * FROM TestIt
END
GO
exec cp.ProcMain2
GO
exec cp.ProcMain
GO
You get the error
Could not find stored procedure 'ProcSub'
Which makes it impossible to compartmentalize procedures into a schema without hard coding the schema into the execution call. Is this by design or a bug as doing a select on tables looks in the procedures schema first.
If anyone has a work around I'd be interested to hear it, although the idea is that I can give a developer two stored procedures which call each other and can put into whatever schema they like in 'their' database that they can run for the purpose of being a utility that looks at the objects of another given schema in the same database.
I have looked to see if I might be able to get round it with Synonyms but they seem to have the same problem associated with them.
This is by design, because the default schema is not set for the user calling the procedure.
When schema is not specified in the query, sql server will try the default schema first and then the dbo (if different) schema. So you need to set the default schema or make your procedure creation using fully qualified names.
Check out:
Beginning with SQL Server 2005, each user has a default schema. The
default schema can be set and changed by using the DEFAULT_SCHEMA
option of CREATE USER or ALTER USER. If DEFAULT_SCHEMA is left
undefined, the database user will have dbo as its default schema.
https://technet.microsoft.com/en-us/library/ms190387%28v=sql.105%29.aspx
Related
I am attempting to prevent usage of the default schema of "dbo" in my SQL Server databases. This is being applied to an existing long term project with ongoing maintenance where the developers also manage the SQL Server (are all sysadmin).
This is for the main reason to allow better dependency tracking between code and the SQL Server objects so that we can slowly migrate to a better naming convention. Eg. "dbo.Users", "dbo.Projects", "dbo.Categories" in a DB are nearly impossible to find in code once created because the "dbo." is often left out of SQL Syntax.
However a proper defined schema requires the usage in code. Eg. "Tracker.Users", "Tracker.Projects", etc ...
Even though we have standards set to not use "dbo" for objects it is still accidentally occurring due to management/business pressures for speed to develop.
Note: I'm creating this question simply to provide a solution someone else can find useful
EDIT: As pointed out, for non-sysadmin users the security option stated is a viable solution, however the DDL Trigger solution will also work on sysadmin users. The case for many small teams who have to manage there own boxes.
I feel like it would be 10,000 times simpler to just DENY ALTER on the dbo schema:
DENY ALTER ON SCHEMA::dbo TO [<role(s)/user(s)/group(s)>];
That's not too handy if everyone connects as sa but, well, fix that first.
The following Database DLL Trigger causes error feedback in both the SQL Manager GUI and via Manual TSQL code attempts to create an object for the types specified.
It includes a means to have a special user and provides clear feedback to the user attempting the object creation. It also works to raise the error with users who are sysadmin.
It does not affect existing objects unless the GUI/SQL tries to DROP and CREATE an existing "dbo" based object.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TRIGGER [CREATE_Prevent_dbo_Usage_2] ON DATABASE
FOR CREATE_TABLE, CREATE_VIEW, CREATE_PROCEDURE, CREATE_FUNCTION
AS
DECLARE #E XML = EVENTDATA();
DECLARE #CurrentDB nvarchar(200)=#E.value('(/EVENT_INSTANCE/DatabaseName)[1]', 'NVARCHAR(2000)');
DECLARE #TriggerFeedbackName nvarchar(max)=#CurrentDB+N'.CREATE_Prevent_dbo_Usage'; -- used to feedback the trigger name on a failure so the user can disable it (or know where the issue is raised from)
DECLARE #temp nvarchar(2000)='';
DECLARE #SchemaName nvarchar(2000)=#E.value('(/EVENT_INSTANCE/SchemaName)[1]', 'NVARCHAR(2000)');
DECLARE #ObjectName nvarchar(2000)=#E.value('(/EVENT_INSTANCE/ObjectName)[1]', 'NVARCHAR(2000)');
DECLARE #LoginName nvarchar(2000)=#E.value('(/EVENT_INSTANCE/LoginName)[1]', 'NVARCHAR(2000)');
DECLARE #CurrentObject nvarchar(200)=''; -- Schema.Table
IF #LoginName NOT IN ('specialUser') BEGIN -- users to exclude in evaluation.
IF CASE WHEN #SchemaName IN ('dbo') THEN 1 ELSE 0 END = 1 BEGIN -- is a DBO attempt to create.
SET #CurrentObject = #SchemaName+'.'+#ObjectName; -- grouped here for easy cut and paste/modify.
SET #temp='Cannot create "'+#CurrentObject+'".
This Database "'+#CurrentDB+'" has had creation of "dbo" objects restricted to improve code maintainability.
Use an existing schema or create a new one to group the purpose of the objects/code.
Disable this Trigger TEMPORARILY if you need to do some advanced manipulation of it.
(This message was produced by "'+#TriggerFeedbackName+'")';
throw 51000,#temp,1;
END
END
GO
ENABLE TRIGGER [CREATE_Prevent_dbo_Usage] ON DATABASE
GO
Let's say that in a multi-schema db we have these 2 procs:
Create proc S1004.proc1
As
Exec proc2
GO
Create proc S1004.proc2
As
Select 1
Then, when I try to run proc1 from sa login, sql issues an error : Could not find stored procedure 'proc2'.
I know that if we add schema to proc2 in the body of proc1, then it can resolve the schema.
Is there any other solution for this problem.
If you can use your schema in the 'scope' of user (be aware that users and schemas are separated in SQL Server), you could get away with this:
CREATE USER S1004 FOR LOGIN S1004 WITH DEFAULT_SCHEMA = S1004;
GO
CREATE PROCEDURE S1004.proc1
WITH EXECUTE AS 'S1004'
AS
EXECUTE proc2
GO
CREATE PROCEDURE S1004.proc2
AS
SELECT 1
EXEC S1004.proc1
What happens here is that you create user S1004 with a default schema with the same name. That schema will be searched for object when object is not found immediately in the scope of current schema.
When you need to resolve your schema in a procedure, you run the procedure in the context of that user (see WITH EXECUTE AS 'S1004') and schema resolution will succeed.
However, pay attention that this changes execution scope to the another user. It's a workaround, but under the circumstances it's the best you can get.
I have a stored procedure in SQL Server 2008 such as:
CREATE PROCEDURE [dbo].[test]
AS
BEGIN
SET NOCOUNT ON;
SELECT user_name();
SELECT SCHEMA_NAME()
SELECT * FROM MyView
END
I have a view named testuser.MyView. I then call the SP using:
exec as user = 'testuser' exec test
This shows the user_name and SCHEMA_NAME are both set to testuser
However I also get a Invalid object name 'MyView'. message, as the SP is still looking up the view name in the dbo schema.
Is there anyway to change how the SP is executed so the MyView object references testuser.MyView without having to use a fully qualified name?
I am trying to use a single set of many stored procedures, on identical table structures in different schemas. I really want to avoid rewriting all the SPs using dynamic SQL, or to create copies of all the SPs each using qualified names.
Unqualified object names are resolved using the module owner's default schema. This behavior can't be changed so you'll need to resort to the other methods you mentioned.
Since you already create separate tables and views for each user with the same structure, why not create stored procedures in the user's schema at the same time?
I want to know what are the default permissions for sql server stored procedures.
For example, I created a user in database but no role or no permissions granted except execute permission like:
GRANT EXECUTE ON SCHEMA :: [dbo] TO [newUser]
later on me (as a sa login) created a stored procedure that makes just a select from a table like:
CREATE PROCEDURE dbo.selectX AS
BEGIN
SELECT ID, NAME FROM MyTable
END
and my user [newUser] can execute and see the result of select statement like:
EXEC dbo.selectX
up until now, I can assume that stored procedures have default permissions that my user don't have.
I tried other dmls like UPDATE, INSERT, DELETE and user managed to execute procedures successfully.
But! when I created a proceure with ddl as create table .. such as:
CREATE PROCEDURE dbo.crtNT AS
BEGIN
CREATE TABLE del(id int) --for test
END
and when the newUser executes this procedure error occurs like:
CREATE TABLE permission denied in database 'MyDb'
And here is my question: By default, do stored procedures have permission with DML(select, insert, update vs.) but not with DDL(create, drop table vs.)? Because I didn't see any explanation about this situation in Books Online or anywhere else.
Any comments will be welcomed.
You are correct in your assumption that DDL statements are treated differently.
The procedures with DML statements work through the mechanism of Ownership Chaining however as this quote from the EXECUTE AS documentation states
Remember that ownership chaining applies only to DML statements.
I'm working on the next update for StackQL.
One thing I want to do is have the ability to query over several releases. So when I loaded the October data, for example, I didn't delete the old September database. It's still out there. In fact, you can even still query it by including the database name like this:
select top 10 * from SO_Sept09..Posts
This will be even more important as they start providing data for ServerFault and SuperUser.
But I don't like having a whole bunch of databases out there to support this. I'd much rather put all the data in the same database and separate each distinct set into it's own schema. But to make this possible, I need to be able to set a default schema as part of the stored procedure that runs the query, based on a parameter passed to the stored procedure that tells it which database the user selected from a future drop down list to appear in the tool bar.
Queries at StackQL are eventually just passed to the exec() function like this:
exec(#QueryText)
Is there anything I can do either in the stored procedure or prepend to the QueryText string (ala USE [DatabaseName]) to set the default schema used in a query?
There are pieces of how to do this in various places here, but not altogether. The way to do this is:
Make a unique login & user for each schema
Make these users the owners of each different schema.
Set each such user's default schema to be the schema that they own.
Use the syntax EXECUTE ('sql commands') AS USER = 'schema-owner' to execute your SQL commands in the context of that default schema.
The following script demonstrates this:
--====== Create the Login for the User:
CREATE LOGIN [UserTest1] WITH PASSWORD='whatever', DEFAULT_DATABASE=[TestUsers], DEFAULT_LANGUAGE=[us_english]
GO
--====== Make a User for the Login:
CREATE USER [UserTest1] FOR LOGIN [UserTest1]
GO
--====== Make a Schema owned by the User and default to it:
-- (I assume that you already have the schemas)
CREATE SCHEMA [UserTest1] AUTHORIZATION [UserTest1]
GO
ALTER USER [UserTest1] WITH DEFAULT_SCHEMA=[UserTest1]
GO
--====== Make a sProc in dbo
CREATE PROCEDURE [dbo].[TestSchema_Exec] AS
SELECT 'executing in schema [dbo]'
GO
--====== Make a similar sProc in New Schema
CREATE PROCEDURE [UserTest1].[TestSchema_Exec] AS
SELECT 'executing in schema [UserTest1]'
GO
--========= Demonstrate that we can switch Default Schemas:
EXEC('TestSchema_Exec')
EXEC('TestSchema_Exec') AS USER = 'UserTest1'
Other than modifying #QueryText itself, the only thing I can think of is a user's default schema:
ALTER USER SO_Sept09_Reader WITH DEFAULT_SCHEMA = SO_Sept09
...and then connect as a different user for each schema you want to use. Hackity hack.
But if your query is dynamically constructed anyway (and I'm sure you know why that's often not a great idea), it might be easiest to just add a schema placeholder to the query text, and pass the schema name along with the query to a replacement function.
Another possibility is to generated copies of each SP in each schema, the unmodified table name in an SP refers to tables in the same schema.
Note this doesn't work with dynamic SQL inside such an SP:
CREATE PROCEDURE schema_a.SP
#somesql AS varchar(MAX)
AS
BEGIN
EXEC ( #somesql )
END
CREATE PROCEDURE schema_b.SP
#somesql AS varchar(MAX)
AS
BEGIN
EXEC ( #somesql )
END
Won't work, because the schema affinity is lost inside the EXEC.
While this:
CREATE PROCEDURE schema_a.SP
AS
BEGIN
SELECT * FROM tbl -- Will use schema_a.tbl first
END
CREATE PROCEDURE schema_b.SP
AS
BEGIN
SELECT * FROM tbl -- Will use schema_b.tbl first
END
works fine.
Similarly:
EXEC ( 'EXEC schema_a.SP' )
would obviously work fine.
Untried, but: could you combine the different schemas' data into one view, and add a column calling out the schema name?
CREATE VIEW AllPosts AS
SELECT Data1, Data2, 'Sept09' AS Partition FROM SO_Sept09..Posts
UNION ALL
SELECT Data1, Data2, 'Oct09' AS Partition FROM SO_Oct09..Posts
...
SELECT * FROM AllPosts WHERE Partition = 'Sept09'
SELECT * FROM dbo.AllPosts('Sept09') -- if use table-valued function instead
Okay, I have a new way to do this that might work a little better for me. It's a variant on my comment to Michael Petrotta's answer:
But it does give me the idea to maybe have several users and pick the connection string on the fly.
What I will do is have just one user for executing these queries, but I will swap out the connection string on the fly to specify the correct Initial Catalog.