SQLAlchemy: reflection on DBs with limited permissions? - python

I'm trying to reflect some tables from a legacy DB (have no control over how it is set up, can't change it). The schema has a ton of tables in it, most of which aren't relevant to me, and which I don't have access to. When I try to reflect from the tables I do have access to, SQLAlchemy errors out because it can't do a SHOW command on some table I don't care about. E.g.:
fooTable = Table('foo', meta)
insp = reflection.Inspector.from_engine(eng)
insp.reflecttable(fooTable, include_columns=("id", "name"))
sqlalchemy.exc.OperationalError: (OperationalError) (1142, "SHOW command denied to user 'xxx'#'yyy' for table 'bar'") 'SHOW CREATE TABLE `schema`.`bar`' ()
There is an FK in fooTable to barTable (not on the id or name columns), but since I have no read access to or interest in barTable at all I'd really just prefer if SQLAlchemy could ignore it. Is this possible? I would be OK with a solution that doesn't try to load any relations at all, since I just need to read the data in individual tables.

Related

pd.DataFrame.to_sql() is prepending the server name and username to the table name

I have a Pandas dataframe df which I want to push to a relational database as a table. I setup a connection object (<Connection>) using SQLAlchemy (pyodbc is the connection engine), and called the command
df.to_sql(<Table_Name>, <Connection>)
which I was able to confirm was written as a table to the desired relational database by visual examination of it in SQL Server Management Studio (SSMS). But in the left-hand-side list of databases and their tables in SSMS I see that it has named it
<Sender_Server>\<Username>.<Table_Name>
where <Sender_Server> is (I think) related to the name of the server I ran the Python command from, <Username> is my username on that server, and <Table_Name> is the desired table name.
When I right-click on the table and select to query the top one thousand rows I get a query of the form
SELECT * FROM [<Data_Base_Name>].[<Sender_Server>\<Username>].[<Table_Name>]
which also has the <Sender_Server>\<Username> info in it. The inclusion of <Sender_Server>\<Username> is undesired behaviour in our use case.
How can I instead have the data pushed such that
SELECT * FROM [<Data_Base_Name>].[<Table_Name>]
is the appropriate query?
By default, .to_sql() assumes the default schema for the current user unless schema="schema_name" is provided. Say, for example, the database contains a table named dbo.thing and the database user named joan has a default schema named engineering. If Joan does
df.to_sql("thing", engine, if_exists="append", index=False)
it will not append to dbo.thing but will instead try to create an engineering.thing table and append to that. If Joan wants to append to dbo.thing she needs to do
df.to_sql("thing", engine, schema="dbo", if_exists="append", index=False)

Effective insert-only permissions for peewee tables

I'm wondering what the best strategy is for using insert-only permissions to a postgres db with Peewee. I'd like this in order to be certain that a specific user can't read any data back out of the database.
I granted INSERT permissions to my table, 'test', in postgres. But I've run into the problem that when I try to save new rows with something like:
thing = Test(value=1)
thing.save()
The sql actually contains a RETURNING clause that needs more permissions (namely, SELECT) than just insert:
INSERT INTO "test" ("value") VALUES (1) RETURNING "test"."id"
Seems like the same sql is generated when I try to use query = test.insert(value=1)' query.execute() as well.
From looking around, it seems like you need either grant SELECT privileges, or use a more exotic feature like "row level security" in Postgres. Is there any way to go about this with peewee out of the box? Or another suggestion of how to add new rows with truly write-only permissions?
You can omit the returning clause by explicitly writing your INSERT query and supplying a blank RETURNING. Peewee uses RETURNING whenever possible so that the auto-generated PK can be recovered in a single operation, but it is possible to disable it:
# Empty call to returning will disable the RETURNING clause:
iq = Test.insert(value=1).returning()
iq.execute()
You can also override this for all INSERT operations by setting the returning_clause attribute on the DB to False:
db = PostgresqlDatabase(...)
db.returning_clause = False
This is not an officially supported approach, though, and may have unintended side-effects or weird behavior - caveat emptor.

Django / Postgres write-only database

For a specific security reason, a client has asked if we can integrate a 'write-only' DB into a Django web application. I have tried creating a DB then restricting access to one of its tables in psql via:
REVOKE SELECT ON TABLE testapp_testmodel FROM writeonlyuser;
But then trying to save a model in the Django shell...
p = new TestModel(test_field="testvalue")
p.save(using="writeonlydb")
...generates this error:
ProgrammingError: permission denied for relation testapp_testmodel
Which I assume is because the ORM generated SQL includes a return of the newly created object's id, which counts as a read:
INSERT INTO "testapp_testmodel" ("test_field") VALUES ('testvalue') RETURNING "testapp_testmodel"."id"
My question is therefore, is this basically impossible? Or is there perhaps some other way?

SqlAlchemy Reflection of Oracle Table Not Owned

I'm working with SQLAlchemy to run SQL queries against an Oracle database. I have read access to the database, but the user I have does not own any of the tables I'm working with.
The database updates on a regular basis, so rather than explicitly listing the MetaData, I was hoping to use reflection. I found this question, that describes an issue similar to what I'm having. However, I don't have a way to change ownership of the tables, nor modify the database in any way. I just have read access.
Is there a way to reflect Oracle tables in SQLAlchemy if I don't have ownership of those tables?
(Edit)
Example Code:
engine = create_engine('ORACLE CONNECTION STRING')
metadata = MetaData()
students = Table('students', metadata, autoload=True, autoload_with=engine)
I receive an exception of sqlalchemy.exc.NoSuchTableError: students
However, when I run the following:
results = engine.execute('SELECT * FROM students')
for r in results:
print(r)
I receive the output that I expected from the table, which is a tuple of all the fields for each row.
So instead of trying to reflect a single table, I try to reflect all of them:
metadata.reflect(bind=engine)
print(metadata.tables)
The output is immutabledict({}).
So essentially it's nothing. All of these tables are owned by user A where as I'm logging in with a read-only of user B.
You might have better luck reflecting someone else's tables if you specify the schema (account) you're targeting:
metadata.reflect(bind=engine, schema='userA')
This way, you'll reflect all readable tables belonging to 'userA'. I'm not sure why you're able to query students using engine.execute, though.

django postgresql query not working

I have a postgreSQL database that has a table foo that I've created outside of django. I used manage.py inspectdb to build the model for table foo for me. This technique worked fine when I was using MySQL but with PostgreSQL it is failing MISERABLY. The table is multiple gigabytes and I build it from a text file with PostgreSQL 'COPY'.
I can run raw queries on table foo and everything executes and expected.
For example
foo.objects.raw('bar_sql')
executes as expected.
But running queries like:
foo.objects.get(bar=bar)
throw
ProgrammingError column foo.id does not exist LINE 1: SELECT "foo"."id", "foo"."bar1", "all_...
foo doesn't innately have an id field. As I understand it django is suppose to create one. Have I some how subverted this step when creating the tables outside of django?
Queries run on models whose table was populated threw django run as expected in all cases.
I'm missing something very basic here and any help would be appreciated.
I'm using django 1.6 with postgreSQL 9.3.
Django doesn't modify your existing database tables. It only creates new tables. If you have existing tables, it usually doesn't touch them at all.
"As I understand it django is suppose to create one." --> It only adds a primary key to a table when it creates it, which means you don't need to specify that explicitly in your model, but it won't do anything to an existing table.
So if for example you later on decide to add fields to your models, you have to update your databases manually.
What you need to do in your case is that by doing manual database administration make sure that your table has a primary key, and also that the name of the primary key is "id" (although I am not sure if this is necessary, it is better to do it.) So use a database administration tool, modify your table and add the primary key, and name it id. Then it should start working.

Categories