2012-07-03 23 views
7

Như các tài liệu Android nói, selectionArgs các tham số của phương thức rawQuery được phân tích thành chuỗi.SQLite rawQuery selectionArgs và số nguyên Fields

SQLiteDatabase.rawQuery (String sql, String [] selectionArgs)

selectionArgs: Bạn có thể bao gồm s trong mệnh đề where trong truy vấn, mà sẽ được thay thế bằng các giá trị từ selectionArgs?. Các giá trị sẽ bị ràng buộc dưới dạng Chuỗi.

Nhưng hôm nay, tôi đã phải đối mặt với một vấn đề mất một phần lớn trong ngày của tôi. Hãy tưởng tượng truy vấn sau:

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= 15 

COLUMN_A là INTEGER. Bảng có khoảng 10 hàng tham dự tiêu chí đó. Chạy truy vấn trên trình soạn thảo cơ sở dữ liệu, kết quả luôn đúng, nhưng trên điện thoại thông minh, câu lệnh luôn trả về không có hàng.

Sau một thời gian, một thay đổi truy vấn để:

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= '15' 

và biên tập lại không có hàng, giống như Android. Vì vậy, việc thay đổi truy vấn thành:

SELECT * FROM TABLE_A WHERE CAST(IFNULL(COLUMN_A, 0) as INTEGER) >= '15' 

giải quyết được sự cố. Một thử nghiệm khác đã được thực hiện là:

SELECT * FROM TABLE_A WHERE COLUMN_A >= '15' 

cũng, trả lại kết quả chính xác.

Điều này dường như là một vấn đề liên quan đến cách Android giới hạn các tham số cho truy vấn (dưới dạng chuỗi) với mệnh đề IFNULL.

Vì vậy, không ai biết tại sao điều này xảy ra? Có bất kỳ đề xuất nào để giải quyết vấn đề này mà không sử dụng CAST trên truy vấn không?

+0

Tôi gặp sự cố tương tự và tôi đã dành một giờ để tìm lỗi trong ứng dụng của mình. Sau đó, tôi đã tìm thấy bài đăng của bạn. Vì vậy, nó thực sự tuyệt vời bạn đã viết nó ở đây. Một điều nữa là tôi sẽ không bao giờ hiểu triết lý của Android và tại sao CAST này lại phải ở đây .... – user2707175

Trả lời

9

Lý do tại sao bạn liên kết giá trị của bạn với truy vấn ở địa điểm đầu tiên là bạn muốn ngăn chặn SQL-injection attack.

Về cơ bản, bạn gửi truy vấn của mình (bao gồm trình giữ chỗ) vào cơ sở dữ liệu và nói "Truy vấn tiếp theo của tôi sẽ có dạng này và không có biểu mẫu nào khác!". Khi một kẻ tấn công sau đó tiêm một chuỗi truy vấn khác nhau (thông qua một trường mẫu, ví dụ) cơ sở dữ liệu nói "Này, đó không phải là truy vấn bạn nói bạn sẽ gửi!" và ném một lỗi vào bạn.

Vì lệnh (có thể được đưa vào truy vấn thực tế như hiển thị trong bài viết được liên kết) là chuỗi, kiểu dữ liệu chuỗi là chuỗi "nguy hiểm" hơn. Nếu người dùng cố gắng tiêm một số mã vào trường của bạn chỉ nên lấy số và bạn cố gắng truyền/phân tích đầu vào thành số nguyên (trước khi đặt giá trị vào truy vấn của mình), bạn sẽ nhận được ngoại lệ ngay lập tức. Với một chuỗi, không có loại bảo mật như vậy. Do đó, họ phải được thoát khỏi có lẽ. Điều đó có thể là lý do khiến các giá trị liên kết được hiểu là chuỗi.

Ở trên là bongus!Nó không điên nếu các đối số bạn ràng buộc là chuỗi hoặc số nguyên, tất cả chúng đều nguy hiểm như nhau. Ngoài ra, kiểm tra trước các giá trị trong mã của bạn dẫn đến rất nhiều mã soạn sẵn, là dễ bị lỗi và không linh hoạt!


Để ngăn chặn ứng dụng của bạn từ SQL-Tiêm và cũng thúc đẩy nhiều cơ sở dữ liệu viết hoạt động (sử dụng lệnh tương tự nhưng giá trị khác nhau), bạn nên sử dụng "chuẩn bị phát biểu". Lớp chính xác cho viết vào cơ sở dữ liệu trong SDK Android là SQLiteStatement.

Để tạo một tuyên bố chuẩn bị, bạn sử dụng compileStatement()-method của SQLiteDatabase -object của bạn và liên kết các giá trị tương ứng (mà sẽ được thay thế bằng ? -marks trong truy vấn của bạn) bằng cách sử dụng đúng bindXX() -method (được thừa hưởng từ SQLiteProgram):

SQLiteDatabase db = dbHelper.getWritableDatabase(); 
SQLiteStatement stmt = db.compileStatement("INSERT INTO SomeTable (name, age) values (?,?)"); 
// Careful! The index begins at 1, not 0 !! 
stmt.bindString(1, "Jon"); 
stmt.bindLong(2, 48L); 
stmt.execute(); 
// Also important! Clean up after yourself. 
stmt.close(); 

Ví dụ lấy từ câu hỏi cũ này: How do I use prepared statements in SQlite in Android?


Đáng buồn thay, SQLiteStatement không có tình trạng quá tải trả về Cursor, vì vậy bạn không thể sử dụng nó cho SELECT -báo cáo. Đối với những người, bạn có thể sử dụng rawQuery(String, String[]) -method của SQLiteDatabase:

int number = getNumberFromUser(); 
String[] arguments = new String[]{String.valueOf(number)}; 
db.rawQuery("SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= ?", arguments); 

Lưu ý rằng rawQuery() -method mất một String mảng cho các giá trị tham số. Điều này thực sự không điên, SQLite không thể quan tâm ít hơn về loại. Miễn là các biểu diễn chuỗi tương đương với những gì bạn mong đợi trong một truy vấn SQL, bạn vẫn ổn.

+1

Xin chào @lukas. Nhờ phản ứng của bạn nhưng, như đã nói trong các tài liệu, các câu lệnh chuẩn bị không thể được sử dụng để trả về con trỏ. – regisxp

+0

@regisxp Tôi không biết tại sao tôi không thấy điều đó.Tôi đã cập nhật câu trả lời của mình. Kiểm tra nó ra. –

+0

Tại sao số nguyên đúc Android vào 'Chuỗi'? Bạn không thể tiêm các truy vấn SQL bằng các con số, đúng không? Tôi muốn các tham số 'selectionArguments' là một mảng của' Object' và khung làm việc sẽ giữ nguyên các số nguyên như là số nguyên và bảo vệ 'String' khỏi việc tiêm. –

1

Tôi có cùng một vấn đề giống như tôi đoán. Điều mà bạn đang cố gắng đề cập đến là bạn không thể thực hiện các phép tính thực tế về cơ sở dữ liệu sqlite. Tôi đã phát hiện ra rằng vấn đề lớn hơn vấn đề mà bạn đề cập đến. Cơ sở dữ liệu SQLite dường như không hiểu bất kỳ loại trường nào liên quan đến giá trị trường. Có nghĩa là nếu bạn đang cố gắng chèn một giá trị String vào một trường kiểu INTEGER thì việc chèn sẽ không bị khiếu nại.

Vì vậy, vấn đề thậm chí còn lớn hơn như bạn có thể thấy. Mặc dù tôi đã thấy rằng nếu bạn có một cột mà chỉ có số nguyên và bạn thực hiện một tuyên bố nơi như: nơi id = 1 mà không có '' thì có một tập dữ liệu kết quả. Vì vậy, tôi có thể hỏi bạn nếu bạn chắc chắn rằng tuyên bố này không hoạt động: "SELECT * FROM TABLE_A WHERE IFNULL (COLUMN_A, 0)> = 15". Nhưng id id = = '15' không hoạt động vì nó lấy biểu diễn chuỗi của id thực tế là 2 ký tự unicode (!!!) và cố gắng thực hiện toán tử> = đến '15' mà DOES áp dụng.

Lần đầu tiên tôi gặp những vấn đề này làm tôi ngạc nhiên và tôi đã quyết định tự động tạo SQL mà không có tham số ràng buộc và thực hiện như một chuỗi toàn bộ. Tôi biết đó không phải là cách tốt nhất để truy cập cơ sở dữ liệu, không có tham số ràng buộc, nhưng nó là giải pháp và tốt vì lý do bảo mật không quan trọng mặc dù cơ sở dữ liệu của bạn được bảo mật và phương pháp truy cập của bạn là riêng tư đối với Ứng dụng của bạn . Nếu "kẻ xâm nhập" có khả năng truy cập cơ sở dữ liệu bằng điện thoại gốc, anh ta có thể đặt nó trong một SQLite Studio và anh ta đã hoàn thành.

+0

Hi @ 10s. Cám ơn bạn đã góp ý. 'Phiên bản đầu tiên' của ứng dụng của tôi đã sử dụng cùng một kỹ thuật mà bạn đã nói, thay thế các tham số theo cách thủ công. Tôi quyết định thay đổi vì một số cảnh báo tôi nhận được từ hệ điều hành, yêu cầu tôi sử dụng các đối số trên các truy vấn để cải thiện bộ nhớ cache câu lệnh SQLite. Bạn đã nhận được lời khuyên này chưa? – regisxp

+0

Không, may mắn hay không tôi chưa từng gặp loại cảnh báo đó và tôi đang làm việc trên một ứng dụng doanh nghiệp đòi hỏi nhiều thay thế các tham số SQL theo cách thủ công với các "quả" nặng và tham gia, truy vấn phụ, v.v. đã thử nghiệm đầy đủ các khả năng của SQLite trong Android. Bạn có thể xin vui lòng gửi những cảnh báo này để cho tôi một gợi ý? – 10s

+0

Xác nhận? thảo luận? – 10s

Các vấn đề liên quan